/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.test.deployers.vfs.deployer.facelets.support;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Mock of Facelets's Classpath class.
 *
 * @author <a href="mailto:ales.justin@jboss.com">Ales Justin</a>
 */
@SuppressWarnings("unchecked")
public final class Classpath
{
   public static URL[] search(ClassLoader cl, String prefix, String suffix) throws IOException
   {
      Enumeration[] e = new Enumeration[]{
            cl.getResources(prefix),
            cl.getResources(prefix + "MANIFEST.MF")
      };
      Set all = new LinkedHashSet();
      URL url;
      URLConnection conn;
      JarFile jarFile;
      for (int i = 0, s = e.length; i < s; ++i)
      {
         while (e[i].hasMoreElements())
         {
            url = (URL)e[i].nextElement();
            conn = url.openConnection();
            conn.setUseCaches(false);
            conn.setDefaultUseCaches(false);
            if (conn instanceof JarURLConnection)
            {
               jarFile = ((JarURLConnection)conn).getJarFile();
            }
            else
            {
               jarFile = getAlternativeJarFile(url);
            }
            if (jarFile != null)
            {
               searchJar(cl, all, jarFile, prefix, suffix);
            }
            else
            {
               boolean searchDone = searchDir(all, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix);
               if (searchDone == false)
               {
                  searchFromURL(all, prefix, suffix, url);
               }
            }
         }
      }
      return (URL[])all.toArray(new URL[all.size()]);
   }

   private static boolean searchDir(Set result, File file, String suffix) throws IOException
   {
      if (file.exists() && file.isDirectory())
      {
         File[] fc = file.listFiles();
         String path;
         for (int i = 0; i < fc.length; i++)
         {
            path = fc[i].getAbsolutePath();
            if (fc[i].isDirectory())
            {
               searchDir(result, fc[i], suffix);
            }
            else if (path.endsWith(suffix))
            {
               result.add(fc[i].toURL());
            }
         }
         return true;
      }
      return false;
   }

   /**
    * Search from URL.
    * Fall back on prefix tokens if
    * not able to read from original url param.
    *
    * @param result the result urls
    * @param prefix the current prefix
    * @param suffix the suffix to match
    * @param url the current url to start search
    * @throws IOException for any error
    */
   private static void searchFromURL(Set result, String prefix, String suffix, URL url) throws IOException
   {
      boolean done = false;
      InputStream is = getInputStream(url);
      if (is != null)
      {
         try
         {
            ZipInputStream zis;
            if (is instanceof ZipInputStream)
               zis = (ZipInputStream)is;
            else
               zis = new ZipInputStream(is);

            try
            {
               ZipEntry entry = zis.getNextEntry();
               // initial entry should not be null
               // if we assume this is some inner jar
               done = (entry != null);
               while (entry != null)
               {
                  String entryName = entry.getName();
                  if (entryName.endsWith(suffix))
                  {
                     String urlString = url.toExternalForm();
                     result.add(new URL(urlString + entryName));
                  }
                  entry = zis.getNextEntry();
               }
            }
            finally
            {
               zis.close();
            }
         }
         catch (Exception ignore)
         {
         }
      }
      if (done == false && prefix.length() > 0)
      {
         // we add '/' at the end since join adds it as well
         String urlString = url.toExternalForm() + "/";
         String[] split = prefix.split("/");
         prefix = join(split, true);
         String end = join(split, false);
         int p = urlString.lastIndexOf(end);
         url = new URL(urlString.substring(0, p));
         searchFromURL(result, prefix, suffix, url);
      }
   }

   /**
    * Join tokens, exlude last if param equals true.
    *
    * @param tokens the tokens
    * @param excludeLast do we exclude last token
    * @return joined tokens
    */
   private static String join(String[] tokens, boolean excludeLast)
   {
      StringBuffer join = new StringBuffer();
      for (int i = 0; i < tokens.length - (excludeLast ? 1 : 0); i++)
         join.append(tokens[i]).append("/");
      return join.toString();
   }

   /**
    * Open input stream from url.
    * Ignore any errors.
    *
    * @param url the url to open
    * @return input stream or null if not possible
    */
   private static InputStream getInputStream(URL url)
   {
      try
      {
         return url.openStream();
      }
      catch (Throwable t)
      {
         return null;
      }
   }

   /**
    * For URLs to JARs that do not use JarURLConnection - allowed by
    * the servlet spec - attempt to produce a JarFile object all the same.
    * Known servlet engines that function like this include Weblogic
    * and OC4J.
    * This is not a full solution, since an unpacked WAR or EAR will not
    * have JAR "files" as such.
    */
   private static JarFile getAlternativeJarFile(URL url) throws IOException
   {
      String urlFile = url.getFile();
      // Trim off any suffix - which is prefixed by "!/" on Weblogic
      int separatorIndex = urlFile.indexOf("!/");

      // OK, didn't find that. Try the less safe "!", used on OC4J
      if (separatorIndex == -1)
      {
         separatorIndex = urlFile.indexOf('!');
      }

      if (separatorIndex != -1)
      {
         String jarFileUrl = urlFile.substring(0, separatorIndex);
         // And trim off any "file:" prefix.
         if (jarFileUrl.startsWith("file:"))
         {
            jarFileUrl = jarFileUrl.substring("file:".length());
         }
         return new JarFile(jarFileUrl);
      }
      return null;
   }

   private static void searchJar(ClassLoader cl, Set result, JarFile file,
                                 String prefix, String suffix) throws IOException
   {
      Enumeration e = file.entries();
      JarEntry entry;
      String name;
      while (e.hasMoreElements())
      {
         try
         {
            entry = (JarEntry)e.nextElement();
         }
         catch (Throwable t)
         {
            continue;
         }
         name = entry.getName();
         if (name.startsWith(prefix) && name.endsWith(suffix))
         {
            Enumeration e2 = cl.getResources(name);
            while (e2.hasMoreElements())
            {
               result.add(e2.nextElement());
				}
			}
		}
	}
}
