//
// This software is now distributed according to
// the Lesser Gnu Public License.  Please see
// http://www.gnu.org/copyleft/lesser.txt for
// the details.
//    -- Happy Computing!
//
package com.stevesoft.pat;

import java.io.File;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * This class is a different form of Regex designed to work more like the file
 * matching utility of a Unix shell. It is implemented by some simple string
 * transformations: <center>
 * <table border=1>
 * <tr>
 * <td>FileRegex</td>
 * <td>Regex</td>
 * <tr>
 * <td>*</td>
 * <td>.*</td>
 * <tr>
 * <td>.</td>
 * <td>\.</td>
 * <tr>
 * <td>{</td>
 * <td>(?:</td>
 * <tr>
 * <td>{?!</td>
 * <td>(?!</td>
 * <tr>
 * <td>{?=</td>
 * <td>(?=</td>
 * <tr>
 * <td>{??</td>
 * <td>(??</td>
 * <tr>
 * <td></td>
 * <td>)</td>
 * <tr>
 * <td>?</td>
 * <td>.</td>
 * <tr>
 * <td>{,}</td>
 * <td>(|)</td>
 * </table>
 * </center> Note that a FileRegex pattern always ends with the Regex pattern
 * element "$". If you like to experiment, try making FileRegex's and then
 * printing them out. The toString() method does a decompile of the pattern to a
 * standard Regex. Here are some more complete examples: <center> <table *
 * border=3>
 * <tr>
 * <td>FileRegex</td>
 * <td>Regex</td>
 * <tr>
 * <td>*.java</td>
 * <td>.*\.java$</td>
 * <tr>
 * <td>*.{java,html}</td>
 * <td>.*\.(java|html)$</td>
 * <tr>
 * <td>foo.[chC]</td>
 * <td>foo.[chC]$</td>
 * </table>
 * </center>
 */
public class FileRegex extends Regex
{
  /** Build an unitialized FileRegex. */
  public FileRegex()
  {
    dirflag = EITHER;
  }

  /** Build a FileRegex form String s. */
  public FileRegex(String s)
  {
    super(s);
    dirflag = EITHER;
  }

  /**
   * Compile a new pattern. Throws
   * 
   * @exception com.stevesoft.pat.RegSyntax
   *              for nonsensical patterns like "[9-0]+" just as Regex does.
   * @see com.stevesoft.pat#compile(java.lang.String)
   */
  public void compile(String s) throws RegSyntax
  {
    String npat = toFileRegex(s);
    super.compile(npat);
    if (File.separatorChar == '\\') // MS-DOS
    {
      ignoreCase = true;
    }
  }

  /**
   * This is the method required by FileNameFilter. To get a listing of files in
   * the current directory ending in .java, do this:
   * 
   * <pre>
   * File dot = new File(&quot;.&quot;);
   * 
   * FileRegex java_files = new FileRegex(&quot;*.java&quot;);
   * 
   * String[] file_list = dot.list(java_files);
   * </pre>
   */
  public boolean accept(File dir, String s)
  {
    if (dirflag != EITHER)
    {
      File f = new File(s);
      if (f.isDirectory() && dirflag == NONDIR)
      {
        return false;
      }
      if (!f.isDirectory() && dirflag == DIR)
      {
        return false;
      }
    }
    return matchAt(s, 0);
  }

  int dirflag = 0;

  final static int EITHER = 0, DIR = 1, NONDIR = 2;

  /**
   * Provides an alternative to File.list -- this separates its argument
   * according to File.pathSeparator. To each path, it splits off a directory --
   * all characters up to and including the first instance of File.separator --
   * and a file pattern -- the part that comes after the directory. It then
   * produces a list of all the pattern matches on all the paths. Thus
   * "*.java:../*.java" would produce a list of all the java files in this
   * directory and in the ".." directory on a Unix machine. "*.java;..\\*.java"
   * would do the same thing on a Dos machine.
   */
  public static String[] list(String f)
  {
    return list(f, EITHER);
  }

  static String[] list(String f, int df)
  {
    // return list_(f,new FileRegex());
    StringTokenizer st = new StringTokenizer(f, File.pathSeparator);
    Vector v = new Vector();
    while (st.hasMoreTokens())
    {
      String path = st.nextToken();
      list1(path, v, df, true);
    }
    String[] sa = new String[v.size()];
    v.copyInto(sa);
    return sa;
  }

  final static Regex root = new Regex(
          File.separatorChar == '/' ? "/$" : "(?:.:|)\\\\$");

  static void list1(String path, Vector v, int df, boolean rec)
  {
    // if path looks like a/b/c/ or d:\ then add .
    if (root.matchAt(path, 0))
    {
      v.addElement(path + ".");
      return;
    }
    File f = new File(path);
    if (f.getParent() != null && rec)
    {
      Vector v2 = new Vector();
      list1(f.getParent(), v2, DIR, true);
      for (int i = 0; i < v2.size(); i++)
      {
        String path2 = ((String) v2.elementAt(i)) + File.separator
                + f.getName();
        list1(path2, v, df, false);
      }
    }
    else
    {
      File base = new File(path);

      String dir_s = base.getParent();
      if (dir_s == null)
      {
        dir_s = ".";
      }
      File dir = new File(dir_s);

      FileRegex fr = new FileRegex(base.getName());
      if (fr.isLiteral())
      {
        v.addElement(dir_s + File.separator + base.getName());
        return;
      }
      fr.dirflag = df;
      String[] sa = dir.list(fr);
      if (sa == null)
      {
        return;
      }
      for (int i = 0; i < sa.length; i++)
      {
        v.addElement(dir_s + File.separator + sa[i]);
      }
    }
  }

  /**
   * This method takes a file regular expression, and translates it into the
   * type of pattern used by a normal Regex.
   */
  public static String toFileRegex(String s)
  {
    StrPos sp = new StrPos(s, 0);
    StringBuffer sb = new StringBuffer();
    if (sp.incMatch("{?e="))
    {
      char e = sp.thisChar();
      sp.inc();
      if (sp.incMatch("}"))
      {
        sb.append("(?e=" + e + ")^");
      }
      else
      {
        sb.append("^(?e=");
      }
      sp.esc = e;
    }
    int ParenLvl = 0;
    while (!sp.eos())
    {
      if (File.separatorChar == '\\')
      {
        if (sp.escaped())
        {
          sb.append("\\\\");
        }
        sp.dontMatch = false;
      }
      if (sp.incMatch("?"))
      {
        sb.append(".");
      }
      else if (sp.incMatch("."))
      {
        sb.append(sp.esc);
        sb.append('.');
      }
      else if (sp.incMatch("{??"))
      {
        sb.append("(??");
        ParenLvl++;
        // allow negative lookahead to work
      }
      else if (sp.incMatch("{?!"))
      {
        sb.append("(?!");
        ParenLvl++;
        // allow positive lookahead to work
      }
      else if (sp.incMatch("{?="))
      {
        sb.append("(?=");
        ParenLvl++;
      }
      else if (sp.incMatch("{"))
      {
        sb.append("(?:");
        ParenLvl++;
      }
      else if (sp.incMatch("}"))
      {
        sb.append(')');
        ParenLvl--;
      }
      else if (ParenLvl != 0 && sp.incMatch(","))
      {
        sb.append('|');
      }
      else if (sp.incMatch("*"))
      {
        sb.append(".*");
      }
      else
      {
        sb.append(sp.thisChar());
        sp.inc();
      }
    }
    sb.append("$");
    return sb.toString();
  }

  public boolean isLiteral()
  {
    Pattern x = thePattern;
    while (x != null && !(x instanceof End))
    {
      if (x instanceof oneChar)
      {
        ;
      }
      else if (x instanceof Skipped)
      {
        ;
      }
      else
      {
        return false;
      }
      x = x.next;
    }
    return true;
  }
}
