/*
 * BadLinksRecorder.java  1.0
 *
 * Copyright (c) 1999 Emmanuel PUYBARET - eTeks.
 * All Rights Reserved.
 *
 */
 
import java.net.*;
import java.io.*;
import java.util.*;
import java.text.*;
import javax.swing.text.*;
import javax.swing.text.html.*;

public class BadLinksRecorder
{
  private URL       searchedURL;       // URL de depart

  private Vector    parsedHtmlFiles;   // Ensemble des fichiers HTML lus
  private Vector    otherCheckedFiles; // Ensemble des autre types de fichiers
  private Vector    badFiles;          // Ensemble des fichiers incorrects

  private Hashtable htmlFiles;         // Hashtable ayant pour cle une URL et
                                       // pour valeur les documents HTML lus
  private int       badURLsCount;      // Nombre de mauvaises URLs
  private int       badAnchorsCount;   // Nombre d'ancres non definies

  public BadLinksRecorder (String searchedURL) throws IOException
  {
    this.searchedURL = new URL (searchedURL);
    // Tentative d'ouverture pour verifier la validite de l'URL
    // Declenchement d'une exception en cas de probleme
    InputStream inputStream = getInputStream (this.searchedURL);
    inputStream.close ();
  }

  private InputStream getInputStream (URL url) throws IOException
  {
    // Tentative d'ouverture du fichier
    URLConnection connection = url.openConnection ();
    // Si le fichier est accede via le protocole http
    // verification si un code 4xx n'a pas ete renvoye
    if (   connection instanceof HttpURLConnection
        && ((HttpURLConnection)connection).getResponseCode () / 100 == 4)
      throw new IOException ();
    return connection.getInputStream ();
  }

  // Methode d'interrogation des resultats du parsing
  public Vector getHTMLFiles ()
  {
    return parsedHtmlFiles;
  }

  public Vector getMalformedURLs (URL file)
  {
    return ((HTMLBadLinks)htmlFiles.get (file)).getMalformedURLs ();
  }

  public Vector getBadURLs (URL file)
  {
    return ((HTMLBadLinks)htmlFiles.get (file)).badURLs;
  }

  public Vector getBadAnchors (URL file)
  {
    return ((HTMLBadLinks)htmlFiles.get (file)).badAnchors;
  }

  public int getBadURLsCount ()
  {
    return badURLsCount;
  }

  public int getBadAnchorsCount ()
  {
    return badAnchorsCount;
  }

  // Parcours du fichier HTML donne au constructeur et de tous les fichiers
  // qui lui sont lies.
  public boolean parseFiles ()
  {
    parsedHtmlFiles   = new Vector (); // Ensemble des fichiers HTML rencontres
    otherCheckedFiles = new Vector (); // Ensemble des autre types de fichiers
    htmlFiles         = new Hashtable ();

    badFiles          = new Vector ();

    parsedHtmlFiles.addElement (searchedURL);

    // Parcours de toutes les urls qui sont ajoutees a parsedHtmlFiles
    HTMLEditorKit  html = new HTMLEditorKit ();
    for (int i = 0; i < parsedHtmlFiles.size (); i++)
    {
      URL    searchURL = (URL)parsedHtmlFiles.elementAt (i);
      Reader urlReader = null;
      try
      {
        InputStream inputStream = getInputStream (searchURL);
        urlReader = new BufferedReader (
                      new InputStreamReader (inputStream));
      }
      catch (IOException e)
      {
        // En cas d'echec, ajout a l'ensemble des mauvais fichiers
        badFiles.addElement (searchURL);
        continue;
      }

      try
      {
        System.out.println ("Lecture de " + searchURL + "...");

        // Creation d'un document HTML ajoute a l'ensemble htmlFiles
        HTMLBadLinks doc = new HTMLBadLinks (searchURL, parsedHtmlFiles,
                                             otherCheckedFiles);
        htmlFiles.put (searchURL, doc);

        // Demarrage de la lecture et du parsing du fichier HTML
        html.read (urlReader, doc, 0);
        urlReader.close ();

        for (Enumeration e = doc.getURLs ().elements ();
             e.hasMoreElements (); )
          try
          {
            URL url = (URL)e.nextElement ();
            // Construction de l'URL sans ancre
            URL urlWithNoAnchor = new URL (url.getProtocol (), url.getHost (),
                                           url.getPort (), url.getFile ());
            String lowerCaseFile = url.getFile ().toLowerCase ();
            if (   lowerCaseFile.endsWith (".htm")
                || lowerCaseFile.endsWith (".html"))
            {
              // Si le fichier est un fichier HTML, ajout a l'ensemble
              // des fichiers HTML (verification de l'extension en minuscules)
              if (!parsedHtmlFiles.contains (urlWithNoAnchor))
                parsedHtmlFiles.addElement (urlWithNoAnchor);
            }
            else
              // Pour les autres types de fichier (GIF, JPG,...), ajout
              // a l'ensemble des autres fichiers
              if (!otherCheckedFiles.contains (url))
                otherCheckedFiles.addElement (url);
          }
          catch (MalformedURLException exception)
          { } // Ne peut arriver
      }
      catch (IOException e)
      {
        System.out.println ("Probleme de lecture de " + searchURL);
      }
      catch (BadLocationException e)
      { }
    }

    // Suppression des mauvais fichiers de parsedHtmlFiles
    for (int i = 0; i < parsedHtmlFiles.size (); )
      if (badFiles.contains (parsedHtmlFiles.elementAt (i)))
        parsedHtmlFiles.removeElementAt (i);
      else
        i++;

    // Suppression des mauvais fichiers de otherCheckedFiles
    for (int i = 0; i < otherCheckedFiles.size (); )
    {
      URL otherFile = (URL)otherCheckedFiles.elementAt (i);
      try
      {
        // Tentative d'ouverture du fichier
        InputStream inputStream = getInputStream (otherFile);
        inputStream.close ();
        i++;
      }
      catch (IOException e)
      {
        badFiles.addElement (otherFile);
        otherCheckedFiles.removeElementAt (i);
      }
    }

    badURLsCount    = 0;
    badAnchorsCount = 0;

    // Recherche des URLs et ancres mauvaises dans tous les fichiers lus
    for (Enumeration e = htmlFiles.elements ();
         e.hasMoreElements (); )
    {
      HTMLBadLinks parsedDocument = (HTMLBadLinks)e.nextElement ();
      badURLsCount += parsedDocument.getMalformedURLs ().size ();

      // Parcours de toutes les URLs du fichier
      for (Enumeration eURL = parsedDocument.getURLs ().elements ();
           eURL.hasMoreElements (); )
        try
        {
          URL url = (URL)eURL.nextElement ();
          // Construction de l'URL sans ancre
          URL urlWithNoAnchor = new URL (url.getProtocol (), url.getHost (),
                                         url.getPort (), url.getFile ());
          // Verification si l'URL sans ancre est dans les mauvais fichiers
          if (badFiles.contains (urlWithNoAnchor))
          {
            parsedDocument.badURLs.addElement (urlWithNoAnchor);
            badURLsCount++;
          }
          else if (   !otherCheckedFiles.contains (url)
                   && url.getRef () != null)
          {
            HTMLBadLinks htmlDocument = (HTMLBadLinks)htmlFiles.get (urlWithNoAnchor);
            // Recherche de l'existence de l'ancre dans le fichier indique
            if (!htmlDocument.getAnchors ().contains (url.getRef ()))
            {
              parsedDocument.badAnchors.addElement (url);
              badAnchorsCount++;
            }
          }
        }
        catch (MalformedURLException ex)
        { } // Ne peut arriver
    }

    // Tri bulle simple sur le vecteur parsedHtmlFiles
    // pour une sortie des fichiers par ordre alphabetique
    boolean sorted;
    do
    {
      sorted = true;
      for (int i = 0; i < parsedHtmlFiles.size () - 1; i++)
      {
        URL url1 = (URL)parsedHtmlFiles.elementAt (i);
        URL url2 = (URL)parsedHtmlFiles.elementAt (i + 1);
        if (url1.toString ().compareTo (url2.toString ()) > 0)
        {
          parsedHtmlFiles.setElementAt (url2, i);
          parsedHtmlFiles.setElementAt (url1, i + 1);
          sorted = false;
        }
      }
    }
    while (!sorted);

    // Renvoie true si pas d'erreur rencontree
    return badURLsCount == 0 && badAnchorsCount == 0;
  }

  // Classe derivee de HTMLDocumentLinks qui memorise en plus les URLs
  // et les ancres mauvaises
  private class HTMLBadLinks extends HTMLDocumentLinks
  {
    Vector badAnchors = new Vector ();
    Vector badURLs    = new Vector ();

    public HTMLBadLinks (URL    file,
                         Vector htmlFiles,
                         Vector otherFiles)
    {
      super (file);
    }
  }
  
  // Methode main () d'exemple de mise en oeuvre 
  // (prend en argument un nom de fichier HTML de depart  
  // sous forme d'URL, par exemple file:/disk/dir1/index.html)
  public static void main (String args [])
  {
    try
    {
      BadLinksRecorder recorder = new BadLinksRecorder (args [0]);
      if (recorder.parseFiles ())
        System.out.println ("\nToutes les URL et ancres sont correctes.");
      else
      {
        // Pour chacun des fichiers HTML lus, listing des erreurs
        for (Enumeration e = recorder.getHTMLFiles ().elements ();
             e.hasMoreElements (); )
        {
          URL htmlFile = (URL)e.nextElement ();
          for (Enumeration e2 = recorder.getMalformedURLs (htmlFile).elements ();
               e2.hasMoreElements (); )
            System.out.println (  "Dans le fichier " + htmlFile 
                                + " l'URL " + e2.nextElement () 
                                + " est incorrecte.");
          for (Enumeration e2 = recorder.getBadURLs (htmlFile).elements ();
               e2.hasMoreElements (); )
            System.out.println (  "Dans le fichier " + htmlFile 
                                + " l'URL " + e2.nextElement () 
                                + " n'existe pas.");
          for (Enumeration e2 = recorder.getBadAnchors (htmlFile).elements ();
               e2.hasMoreElements (); )    
          {
            URL url = (URL)e2.nextElement ();
            System.out.println (  "Dans le fichier " + htmlFile 
                                + " l'ancre" + url.getRef ()
                                + " de l'URL " + url
                                + " est inconnue.");
          }
        }

        System.out.println (  "" + recorder.getBadURLsCount ()
                            + " URL et " + recorder.getBadAnchorsCount () 
                            + " ancres sont incorrectes.\n");
      }    
      
      // Liste de tous les fichiers HTML lus
      System.out.println ("\nListe des " + recorder.getHTMLFiles ().size () + 
                          " fichiers verifies :\n");
      for (Enumeration e = recorder.getHTMLFiles ().elements ();
           e.hasMoreElements (); )
        System.out.println (e.nextElement ());
    }  
    catch (IOException e)
    {
      System.out.println ("Probleme d'acces a l'URL : " + args [0]);
    }
  }      
}
