/*::.
==================================================================================================================================
=================================================¦ Copyright © 2001 Allen Baker ¦=================================================
                                                 +------------------------------+
File:       DirFilter.java
Originator: Allen Baker (2001.09.02)
LayoutRev:  5
================================================================================================================================== */



/*.
==========================================================================================
Package
------------------------------------------------------------------------------------------ */
package cosmicabyss.com.lib;



/*.
==========================================================================================
Imports
------------------------------------------------------------------------------------------ */
import java.io.*;
import java.util.*;



/*::
======================================================================================================================== *//**
Instances of this class are used to select file names that match a pattern. The File class's list() method uses these
objects to filter directory listings. Patterns are the familiar file name patterns that users are accustomed to using
with their OS's command line interpreter.

<P>
   <DL>
      <DT>
         <B>
            Here are some example patterns:
         </B>
         <DD>
            <BLOCKQUOTE>
               <PRE id="unindent">
                  * *.* *.txt t*.i* ??.z* bph00?00.iwa
               </PRE>
            </BLOCKQUOTE>
         </DD>
      </DT>
      <DT>
         <B>
            Example usage:
         </B>
         <DD>
            <BLOCKQUOTE>
               <PRE id="unindent">
                  =========================================================================================
                  Write true to
                  sysout if foo.txt exists in directory d:\test, matches the pattern f*.t?? And is a normal file; otherwise
                  write false to sysout. Pattern matching is set to be case-insensitive.
                  -----------------------------------------------------------------------------------------
                  DirFilter df = new DirFilter("f*.t??",true);

                  System.out.println
                     (
                     df.accept( new File("d:\test"), "foo.txt" )
                     );
               </PRE>
            </BLOCKQUOTE>
         </DD>
      </DT>
      <DT>
         <B>
            View Source:
         </B>
         <DD>
            <A href="DirFilter.java.html">
               DirFilter.java
            </A>
         </DD>
      </DT>
      <DT>
         <B>
            Author:
         </B>
         <DD>
            <A href="mailto:sourcecode.v01@cosmicabyss.com">
               Allen Baker
            </A>
         </DD>
      </DT>
   </DL>
*//*
======================================================================================================================== */
public class DirFilter implements FilenameFilter
   {



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   Subdirectories will be accepted by the filter.

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#000">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static final boolean  INC_SUBDIRS = true;



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   Subdirectories will not be accepted by the filter.

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#001">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static final boolean  NO_SUBDIRS  = false;



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an object that will compare file names to the given pattern.<P>

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#002">View source</A>

   @param
      pPattern is the pattern against which this object compares file names.
   @param
      pSubdirectories sets whether or not this object's filter method accepts sub directory names. A
      value of DirFilter.NO_SUBDIRS (or false) causes subdirectories to be excluded. A value of
      DirFilter.INC_SUBDIRS (or true) causes subdirectories to be included.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> DirFilter(Type pPattern, boolean pSubdirectories)
      {
      iPattern        = replaceWildcards(XString.toXString(pPattern));
      iSubdirectories = pSubdirectories;
      iCaseSensitive  = Util.fileNamesAreCaseSensitive();

      if ( ! iCaseSensitive)
         {
         iPattern = iPattern.toLowerCase();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an object that will compare file names to the given pattern, defaulting to
   normal-file-only mode. In other words, the filter only accepts the names of normal files, not
   directories

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#003">View source</A>

   @param
      pPattern is the pattern against which this object compares file names.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> DirFilter(Type pPattern)
      {
      iPattern        = replaceWildcards(XString.toXString(pPattern));
      iSubdirectories = NO_SUBDIRS;
      iCaseSensitive  = Util.fileNamesAreCaseSensitive();

      if ( ! iCaseSensitive)
         {
         iPattern = iPattern.toLowerCase();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method returns true if the file name matches the pattern.<P> <P> The signature of this method
   is defined by the FilenameFilter interface.

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#004">View source</A>

   @return
      True if and only if the name should be included in the file list; false otherwise.

   @param
      pDirName is the directory in which the file was found.
   @param
      pFileName is the name of the file and it is what is compared to the pattern.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public boolean accept(File pDirName, String pFileName)
      {
      /*.
      ==========================================================================================
      Windows will match everything without an extension to a file specifier that ends in the
      '.' character. Java's regular expression evaluator will only match names that actually
      have a '.' at the end of their name to a file specifier that ends in the '.' character. To
      make the regular expression evaluator work the way Windows works, this code ads an actual
      '.' to the end of any file specifier that is extensionless.
      ------------------------------------------------------------------------------------------ */
      ifb>
         (
         Util.isWindowsOS()             &&   // were on windows
         iPattern.endsWith("\\.")       &&   // regex is looking for actual dot
         (pFileName.indexOf('.')==(-1))      // file spec is extensionless
         )
         {
         pFileName = pFileName + ".";
         }
      /*.
      ==========================================================================================
      If we are in a case insensitive environment (Windows) convert everything to lower case.
      ------------------------------------------------------------------------------------------ */
      String fileName = (iCaseSensitive)? pFileName : pFileName.toLowerCase();
      /*.
      ==========================================================================================
      If we are including subdirectories as well as normal files, then just see if the file name
      matches the pattern and return true if it does
      ------------------------------------------------------------------------------------------ */
      if (iSubdirectories)
         {
         return (fileName.matches(iPattern.string()));
         }
      /*.
      ==========================================================================================
      Otherwise, we're excluding subdirectories and only looking for normal files, so also check
      if a matching name is the name of a normal file.
      ------------------------------------------------------------------------------------------ */
      else
         {
         returnb>
            (
            (fileName.matches(iPattern.string()))
            &<_AMP_AMP_COLON_>
            (new File(pDirName,fileName)).isFile()
            );
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the ConsoleStream for this object.

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#005">View source</A>

   @return
      A reference to this object

   @param
      pConsole is a ConsoleStream through which this objects sends its output.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public DirFilter setOut(ConsoleStream pConsole)
      {
      cOut = pConsole;
      return this;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This boilerplate method is used for testing an instantiated object of this class and may include any
   code the developer chooses.

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#006">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public DirFilter test() throws Exception
      {
      cOut.titledPrintf
         (
         "\"HELLO WORLD!\"",
         "%s  %s  %s",
         "I'm an object of the", CLASS_NAME, "class, and I approved this message."
         );
      return this;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Protected  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Private  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*.
   ==========================================================================================
   Class Constants
      <BLOCKQUOTE>
         <PRE id="unindent">
            CLASS_NAME    : the name of this class
            DFLT_LINE_LEN : the default line length for word wrapping
         </PRE>
      </BLOCKQUOTE>
   ------------------------------------------------------------------------------------------ */
   private static final XString  CLASS_NAME    = new XString(DirFilter.class.getName());
   private static final int      DFLT_LINE_LEN = ConsoleMessage.defaultLineLength();
   /*.
   ==========================================================================================
   Class variables
      cOut : console output.
   ------------------------------------------------------------------------------------------ */
   private static ConsoleStream  cOut = ConsoleStream.getSingleton();
   /*.
   ==========================================================================================
   Instance variables
      <BLOCKQUOTE>
         <PRE id="unindent">
            iPattern        : the file name expansion pattern to match.
            iSubdirectories : should subdirectories be included in the list of
                              names matching the pattern?
            iCaseSensitive  : should case be taken into account when matching
                              file names with the pattern?
         </PRE>
      </BLOCKQUOTE>
   ------------------------------------------------------------------------------------------ */
   private XString        iPattern        = null;
   private boolean        iSubdirectories = false;
   private boolean        iCaseSensitive  = true;



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method converts a file name specifier to a regular expression. It checks for * and ? In the
   file name specifier and replaces them with the correct regex pattern characters.

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#007">View source</A>

   @return
      xString containing modified wildcard name

   @param
      pFileSpecifier is a file name specifier potentially containing * and ?
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private static XString replaceWildcards(XString pFileSpecifier)
      {
      StringBuffer buffer = new StringBuffer();

      char [] chars = pFileSpecifier.toCharArray();

      for (int i = 0; i < chars.length; ++i)
         {
         if (chars[i] == '*')
            {
            buffer.append(".*");
            }
         else if (chars[i] == '?')
            {
            buffer.append(".");
            }
         /*.
         ==========================================================================================
         Escape any regex metacharacters that might appear in the glob pattern
         ------------------------------------------------------------------------------------------ */
         else if ("+()^$.{}[]|\\".indexOf(chars[i]) != -1)
            {
            buffer.append('\\').append(chars[i]);
            }
         else
            {
            buffer.append(chars[i]);
            }
         }

      return new XString(buffer.toString());

      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Inner Classes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Public Static Methods  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows this class file to be unit tested as a standalone application. It's the method
   that's called when the class is invoked from the command line by using the java application launcher
   - "java". Main() is not a required method, but the practice of putting one in each class and
   wrapping class test code within it allows easy unit testing of the class; and main does not need to
   be removed when testing is complete.

   <P>
      <DL>
         <DT>
            <B>
               Command line usage:
            </B>
            <DD>
               <BLOCKQUOTE>
                  <PRE id="unindent">
                     usage: java cosmicabyss.com.utl.DirFilter directory fileName "pattern"

                        Enclosing the pattern in quotes prevents the command
                        line interpreter from trying to replace wildcard characters
                        itself instead of letting DirFilter do it.

                        The quotes surrounding the pattern are not needed if
                        there are no wildcard characters in the pattern.

                     Examples:

                      java cosmicabyss.com.utl.DirFilter d:\test\testin\bph00005.iwa bph00005.iwa

                      java cosmicabyss.com.utl.DirFilter . Bph00005.iwa "bph0000.iwa"
                  </PRE>
               </BLOCKQUOTE>
            </DD>
         </DT>
      </DL>

   <P><B>Implementation: </B><A HREF="DirFilter.java.html#008">View source</A>

   @param
      pArgs contains the command line arguments with which this class was invoked as an application.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static void main(String[] pArgs) throws Exception
      {
      /*.
      ==========================================================================================
      Greetings !
      ------------------------------------------------------------------------------------------ */
      cOut.banner(CLASS_NAME);
      /*.
      ==========================================================================================
      Stop right here if the cmd line parameters aren't right
      ------------------------------------------------------------------------------------------ */
      if (pArgs.length != 3)
         {
         cOut.print
            (
            "\tusage: cosmicabyss.com.lib.DirFilter directory fileName \"pattern\"\n" +
            "\n"                                                                      +
            "\t  Enclosing the pattern in quotes prevents the command\n"              +
            "\t  line interpreter from trying to replace wildcard characters\n"       +
            "\t  itself instead of letting cosmicabyss.com.lib.DirFilter do it.\n"    +
            "\n"                                                                      +
            "\t  The quotes surrounding the pattern are not needed if\n"              +
            "\t  there are no wildcard characters in the pattern.\n"                  +
            "\n"                                                                      +
            "\tExamples:\n"                                                           +
            "\n"                                                                      +
            "\t  java cosmicabyss.com.lib.DirFilter d:\\test\\testin\\ bph00005.iwa bph00005.iwa\n" +
            "\n"                                                                      +
            "\t  java cosmicabyss.com.lib.DirFilter . bphoooo5.iwa \"bph0000*.iwa\"\n"
            );
         cOut.println();
         System.exit(-1);
         }
      /*.
      ==========================================================================================
      Create an object and send its output to the ConsoleStream
      ------------------------------------------------------------------------------------------ */
      DirFilter  obj = new DirFilter(new XString(pArgs[2]),false);
      /*.
      ==========================================================================================
      Test code
      ------------------------------------------------------------------------------------------ */
      obj.test();
      /*.
      ==========================================================================================
      Write true to sysout if pArgs[1] exists in directory pArgs[0], matches the pattern
      pArgs[2] and is a normal file; otherwise write false to sysout. Pattern matching is set to
      be case-insensitive.
      ------------------------------------------------------------------------------------------ */
      cOut.println
         (
         obj.accept
            (
            new File(pArgs[0]),
            pArgs[1]
            )
         );
      }



   }  // class DirFilter



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Notes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */