/*::.
==================================================================================================================================
=================================================¦ Copyright © 2004 Allen Baker ¦=================================================
                                                 +------------------------------+
File:       MP3FileGroomer.java
Originator: Allen Baker (2004.12.19)
LayoutRev:  5
================================================================================================================================== */



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



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



import org.blinkenlights.id3.*;
import org.blinkenlights.id3.v1.*;
import org.blinkenlights.id3.v2.*;



/*::
======================================================================================================================== *//**
Instances of this class groom the ID3 tags in an MP3 file.<P>

An MP3FileGroomer object first reads all the ID3 tag values from the MP3 file. ID3v1.0, ID3v1.1, and ID3v2.3.0 tags are
all read in. The object then consolidates all the tag values. Tag values from newer version ID3 tags have precedence
over older version tags. For example, if the file contains an ID3v1.1 tag value of "Highway to Hell" for the title and
an ID3v2.3.0 tag value of "Stairway to Heaven" for title, then the ID3v2.3.0 value takes precedence and the consolidated
tag value for the title is set to "Stairway to Heaven".<P>

If the file doesn't contain any value for the artist and/or title tag value, then the object will try to determine the
consolidated artist and/or title from the file name. If the file name is in the standard format, this will work, if not,
the artist and title may end up with incorrect consolidated tag values.<P>

The standard file name format is:
   <BLOCKQUOTE>
      <PRE id="unindent">
         "artist - title.mp3"
      </PRE>
   </BLOCKQUOTE>

After populating the consolidated tag values, the object will apply a set of rules to the values to "fix them up". The
rules include the elimination of duplicate space characters from the values, elimination of underscore characters,
elimination of leading and trailing white space, and other rules.<P>

After "fixing up" the consolidated tag values, the object provides methods that allow the user to "fix up" the
character-case of the tag values, rename the file using the standard format, write the consolidated tags to the file,
display the consolidated tags, display the tags as read from the file, and other services.<P>

When the object writes the consolidated tags to the file, it first removes all existing tags from the file. It then
writes the consolidated tags to both the ID3v1.1 and ID3v2.3.0 tag areas of the file so that the tag values in the file
are consistent across all ID3 versions.


<P>
   <DL>
      <DT>
         <B>
            Example usage:
         </B>
         <DD>
            <BLOCKQUOTE>
               <PRE id="unindent">
                  =========================================================================================
                  create the groomer
                  -----------------------------------------------------------------------------------------
                  MP3FileGroomer  file = new MP3FileGroomer
                     (
                     msgLog,
                     fileName.toString()
                     );

                  =========================================================================================
                  set its in-memory tag values if/as specified on the command line
                  -----------------------------------------------------------------------------------------
                  file.setArtist (artist );
                  file.setTitle  (title  );
                  file.setAlbum  (album  );
                  file.setComment(comment);
                  file.setGenre  (genre  );
                  file.setYear   (year   );
                  file.setTrack  (track  );
                  if (dataFromFileName) file.setTitleAndArtistFromFileName();

                  =========================================================================================
                  capitalize the XString tags
                  -----------------------------------------------------------------------------------------
                  file.setCase();

                  =========================================================================================
                  write the in-memory tags out to the file and rename the file
                  -----------------------------------------------------------------------------------------
                  file.writeTags();
                  file.renameFile();

                  =========================================================================================
                  display the consolidated tags
                  -----------------------------------------------------------------------------------------
                  file.showTags();
               </PRE>
            </BLOCKQUOTE>
         </DD>
      </DT>
      <DT>
         <B>
            View Source:
         </B>
         <DD>
            <A href="MP3FileGroomer.java.html">
               mP3FileGroomer.java
            </A>
         </DD>
      </DT>
      <DT>
         <B>
            Author:
         </B>
         <DD>
            <A href="mailto:sourcecode.v01@cosmicabyss.com">
               Allen Baker
            </A>
         </DD>
      </DT>
   </DL>
*//*
======================================================================================================================== */
public class MP3FileGroomer
   {



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates an instance of the MP3FileGroomer class without logging.

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

   @param
      pFileName is the name of the MP3 file that this object will manipulate the tags in.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> MP3FileGroomer(Type pFileName) throws Exception
      {
      this.initialize(pFileName);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets all the tags in memory from what was read from the file.

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setTagsFromFile() throws Exception
      {
      /*.
      ==========================================================================================
      We're going to set from all these kinds of tags
      ------------------------------------------------------------------------------------------ */
      ID3V1_0Tags    v10Tags  = new ID3V1_0Tags();
      ID3V1_1Tags    v11Tags  = new ID3V1_1Tags();
      ID3V2_3_0Tags  v230Tags = new ID3V2_3_0Tags();
      /*.
      ==========================================================================================
      First we find the v1.0 tag
      ------------------------------------------------------------------------------------------ */
      for (int i=0; i < this.mTags.length; i++)
         {
         if (this.mTags[i] instanceof ID3V1_0Tags)
            {
            v10Tags = (ID3V1_0Tags)this.mTags[i];
            /*.
            ==========================================================================================
            Get all the tag values
            ------------------------------------------------------------------------------------------ */
            try {this.setArtist (v10Tags.getArtist ());} catch(Exception e) {}
            try {this.setTitle  (v10Tags.getTitle  ());} catch(Exception e) {}
            try {this.setAlbum  (v10Tags.getAlbum  ());} catch(Exception e) {}
            try {this.setComment(v10Tags.getComment());} catch(Exception e) {}
            try {this.setGenre  (v10Tags.getGenre  ());} catch(Exception e) {}
            try {this.setYear   (v10Tags.getYear   ());} catch(Exception e) {}
            }
         }
      /*.
      ==========================================================================================
      Then we find the 1.1 tag, overwriting any earlier version tag values we found
      ------------------------------------------------------------------------------------------ */
      for (int i=0; i < this.mTags.length; i++)
         {
         if (this.mTags[i] instanceof ID3V1_1Tags)
            {
            v11Tags = (ID3V1_1Tags)this.mTags[i];
            /*.
            ==========================================================================================
            Get all the tag values
            ------------------------------------------------------------------------------------------ */
            try {this.setArtist (v11Tags.getArtist    ());} catch(Exception e) {}
            try {this.setTitle  (v11Tags.getTitle     ());} catch(Exception e) {}
            try {this.setAlbum  (v11Tags.getAlbum     ());} catch(Exception e) {}
            try {this.setComment(v11Tags.getComment   ());} catch(Exception e) {}
            try {this.setGenre  (v11Tags.getGenre     ());} catch(Exception e) {}
            try {this.setYear   (v11Tags.getYear      ());} catch(Exception e) {}
            try {this.setTrack  (v11Tags.getAlbumTrack());} catch(Exception e) {}
            }
         }
      /*.
      ==========================================================================================
      Then we find the 2.3.0 tag, overwriting any earlier version tag values we found
      ------------------------------------------------------------------------------------------ */
      for (int i=0; i < this.mTags.length; i++)
         {
         if (this.mTags[i] instanceof ID3V2_3_0Tags)
            {
            v230Tags = (ID3V2_3_0Tags)this.mTags[i];
            /*.
            ==========================================================================================
            Get all the tag values
            ------------------------------------------------------------------------------------------ */
            try {this.setArtist (v230Tags.getArtist     ());} catch(Exception e) {}
            try {this.setTitle  (v230Tags.getTitle      ());} catch(Exception e) {}
            try {this.setAlbum  (v230Tags.getAlbum      ());} catch(Exception e) {}
            try {this.setComment(v230Tags.getComment    ());} catch(Exception e) {}
            try {this.setGenre  (v230Tags.getGenre      ());} catch(Exception e) {}
            try {this.setYear   (v230Tags.getYear       ());} catch(Exception e) {}
            try {this.setTrack  (v230Tags.getTrackNumber());} catch(Exception e) {}
            }
         }
      /*.
      ==========================================================================================
      If there still isn't a value for artist, try getting it from the file name
      ------------------------------------------------------------------------------------------ */
      if ((this.mArtist == null) || (this.mArtist.equals("")))
         {
         this.setArtistFromFileName();
         }
      /*.
      ==========================================================================================
      If there still isn't a value for title, try getting it from the file name
      ------------------------------------------------------------------------------------------ */
      if ((this.mTitle == null) || (this.mTitle.equals("")))
         {
         this.setTitleFromFileName();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pArtist is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setArtist(Type pArtist) throws Exception
      {
      if (pArtist != null)
         {
         this.mArtist = this.fixedString(pArtist);
         this.fixArtist();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pTitle is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setTitle(Type pTitle) throws Exception
      {
      if (pTitle != null)
         {
         this.mTitle = this.fixedString(pTitle);
         this.fixTitle();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pAlbum is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setAlbum(Type pAlbum) throws Exception
      {
      if (pAlbum != null)
         {
         this.mAlbum = this.fixedString(pAlbum);
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pComment is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setComment(Type pComment) throws Exception
      {
      if (pComment != null)
         {
         this.mComment = this.fixedString(pComment);
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pGenre is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setGenre(Type pGenre) throws Exception
      {
      if (pGenre != null)
         {
         this.mGenre = this.fixedString(pGenre);
         this.mGenre = this.setCase(this.mGenre);
         try
            {
            this.mGenreOrdinal = ID3V1Tags.Genre.lookupGenre(this.mGenre.string());
            }
         catch (Exception e)
            {
            this.mGenreOrdinal = ID3V1Tags.Genre.Other;
            }
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pGenreOrdinal is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setGenre(ID3V1Tags.Genre pGenreOrdinal) throws Exception
      {
      if (pGenreOrdinal != null)
         {
         this.mGenreOrdinal = pGenreOrdinal;
         this.mGenre        = XString.toXString(pGenreOrdinal.toString());
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pYearString is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setYear(Type pYearString) throws Exception
      {
      if (pYearString != null)
         {
         XString  yrStr = XString.toXString(pYearString);
         yrStr = yrStr.trim();
         if (yrStr.isAllDigits())
            {
            int  value = UMath.decodeDecimalInt(yrStr);
            if ((value >= 0) && (value <= 9999))
               {
               this.mYearString = yrStr.removeLeadingZeros();
               this.mYear       = value;
               }
            }
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pYear is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setYear(int pYear) throws Exception
      {
      if ((pYear >= 0) && (pYear <= 9999))
         {
         this.mYearString = XString.toXString((new Integer(pYear)).toString());
         this.mYear       = pYear;
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pTrackString is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public <Type> void setTrack(Type pTrackString) throws Exception
      {
      if (pTrackString != null)
         {
         XString  trk = XString.toXString(pTrackString);
         trk = trk.trim();
         if (trk.isAllDigits())
            {
            int  value = UMath.decodeDecimalInt(trk);
            if ((value >= 1) && (value <= 255))
               {
               this.mTrack = value;
               }
            }
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the tag value in memory

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

   @param
      pTrack is the tag value to set
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setTrack(int pTrack) throws Exception
      {
      if ((pTrack >= 1) && (pTrack <= 255))
         {
         this.mTrack = pTrack;
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the artist and title tags in memory from the file name. For this method to work the
   file name has to be in the following format:
      <BLOCKQUOTE>
         <PRE id="unindent">
            artist - title.mp3
         </PRE>
      </BLOCKQUOTE>

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setTitleAndArtistFromFileName() throws Exception
      {
      String     name       = this.mFile.getExtensionlessName().string();
      ArrayList  components = UString.tokenizedString(name,"-");

      if (components.size() > 0) this.setArtist((String)components.get(0));
      if (components.size() > 1) this.setTitle ((String)components.get(1));
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the artist tag in memory from the file name. For this method to work the file name
   has to be in the following format:
      <BLOCKQUOTE>
         <PRE id="unindent">
            artist - title.mp3
         </PRE>
      </BLOCKQUOTE>

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setArtistFromFileName() throws Exception
      {
      String     name       = this.mFile.getExtensionlessName().string();
      ArrayList  components = UString.tokenizedString(name,"-");

      if (components.size() > 0) this.setArtist((String)components.get(0));
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the title tag in memory from the file name. For this method to work the file name
   has to be in the following format:
      <BLOCKQUOTE>
         <PRE id="unindent">
            artist - title.mp3
         </PRE>
      </BLOCKQUOTE>

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setTitleFromFileName() throws Exception
      {
      String     name       = this.mFile.getExtensionlessName().string();
      ArrayList  components = UString.tokenizedString(name,"-");

      if (components.size() > 1) this.setTitle((String)components.get(1));
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method applies the character case rules to the XString tag values

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void setCase()
      {
      /*.
      ==========================================================================================
      Apply character case rules to these tag values
      ------------------------------------------------------------------------------------------ */
      this.mArtist  = this.setCase(this.mArtist );
      this.mTitle   = this.setCase(this.mTitle  );
      this.mAlbum   = this.setCase(this.mAlbum  );
      this.mComment = this.setCase(this.mComment);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method writes the tags from memory to the file

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void writeTags() throws Exception
      {
      /*.
      ==========================================================================================
      We're going to set all these kinds of tags
      ------------------------------------------------------------------------------------------ */
      ID3V1_1Tags    v11Tags  = new ID3V1_1Tags();
      ID3V2_3_0Tags  v230Tags = new ID3V2_3_0Tags();
      /*.
      ==========================================================================================
      Fill out the v11 tag object's values
      ------------------------------------------------------------------------------------------ */
      v11Tags.setTitle     (UString.truncString(this.mTitle,      30));
      v11Tags.setArtist    (UString.truncString(this.mArtist,     30));
      v11Tags.setAlbum     (UString.truncString(this.mAlbum,      30));
      v11Tags.setComment   (UString.truncString(this.mComment,    28));
      v11Tags.setYear      (UString.truncString(this.mYearString, 4 ));
      v11Tags.setGenre     (this.mGenreOrdinal);
      v11Tags.setAlbumTrack(this.mTrack);
      /*.
      ==========================================================================================
      Fill out the v230 tag object's values
      ------------------------------------------------------------------------------------------ */
      v230Tags.setTitle      (this.mTitle.string());
      v230Tags.setArtist     (this.mArtist.string());
      v230Tags.setAlbum      (this.mAlbum.string());
      v230Tags.setComment    (this.mComment.string());
      v230Tags.setYear       (this.mYear);
      v230Tags.setGenre      (this.mGenre.string());
      v230Tags.setTrackNumber(this.mTrack);
      /*.
      ==========================================================================================
      Set the tags in the [in-memory] MP3File object
      ------------------------------------------------------------------------------------------ */
      this.mMP3File.setID3Tags(v11Tags);
      this.mMP3File.setID3Tags(v230Tags);
      /*.
      ==========================================================================================
      Remove all the existing tags from the file
      ------------------------------------------------------------------------------------------ */
      this.mMP3File.removeTags();
      /*.
      ==========================================================================================
      Update the actual file to reflect the current state of the MP3File object
      ------------------------------------------------------------------------------------------ */
      this.mMP3File.sync();
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method renames the file in this format:
      <BLOCKQUOTE>
         <PRE id="unindent">
            artist - title.mp3
         </PRE>
      </BLOCKQUOTE>

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

   @return
      True if the file was successfully renamed, false otherwise.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public boolean renameFile() throws Exception
      {
      /*.
      ==========================================================================================
      Construct the new name according to the standard format: "artist - title.mp3"
      ------------------------------------------------------------------------------------------ */
      XString  newFileName = new XString
         (
         this.mArtist.string()  +
         " - "                  +
         this.mTitle.string()   +
         ".mp3"
         );
      /*.
      ==========================================================================================
      Remove any characters that are invalid in file names
      ------------------------------------------------------------------------------------------ */
      newFileName = this.fixedFileName(newFileName);
      /*.
      ==========================================================================================
      Prepending the directory has to be done after fixFileName() because fixFileName() would
      remove the ":" and "\" characters from the dir portion of the file name.
      ------------------------------------------------------------------------------------------ */
      newFileName = new XString
         (
         this.mFile.getCanonicalParent().string() +
         File.separator                           +
         newFileName
         );
      /*.
      ==========================================================================================
      If the new name is the same as the old name, don't do the rename
      ------------------------------------------------------------------------------------------ */
      if (this.mFile.getCanonicalPath().equals(newFileName.string()))
         {
         return true;
         }
      /*.
      ==========================================================================================
      Notify that a file name is changing
      ------------------------------------------------------------------------------------------ */
      cOut.println
         (
         "Renaming ["                  +
         this.mFile.getCanonicalPath() +
         "]   to\n"                    +
         "         ["                  +
         newFileName                   +
         "]"
         );
      /*.
      ==========================================================================================
      Change the name
      ------------------------------------------------------------------------------------------ */
      return this.renameTo(newFileName);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method displays the tags as they are in memory

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void showTags()
      {
      cOut.println
         (
         "Consolidated Tags",
         "File Name     = " +  this.mFile.getName() + "\n" +
         "mArtist       = " +  this.mArtist         + "\n" +
         "mTitle        = " +  this.mTitle          + "\n" +
         "mAlbum        = " +  this.mAlbum          + "\n" +
         "mComment      = " +  this.mComment        + "\n" +
         "mGenre        = " +  this.mGenre          + "\n" +
         "mGenreOrdinal = " +  this.mGenreOrdinal   + "\n" +
         "mYear         = " +  this.mYear           + "\n" +
         "mYearString   = " +  this.mYearString     + "\n" +
         "mTrack        = " +  this.mTrack
         );
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method displays the tags as they were read from the file

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void showFileTags() throws Exception
      {
      /*.
      ==========================================================================================
      Loop through the tags and display the values for each one
      ------------------------------------------------------------------------------------------ */
      for (int i=0; i < this.mTags.length; i++)
         {
         /*.
         ==========================================================================================
         Display the v1 tags
         ------------------------------------------------------------------------------------------ */
         if (this.mTags[i] instanceof ID3V1_0Tags)
            {
            ID3V1_0Tags oID3V1_0Tags = (ID3V1_0Tags)this.mTags[i];
            cOut.println("ID3 V1.0 Tags",oID3V1_0Tags.toString());
            }
         /*.
         ==========================================================================================
         Display the v1.1 tags
         ------------------------------------------------------------------------------------------ */
         if (this.mTags[i] instanceof ID3V1_1Tags)
            {
            ID3V1_1Tags oID3V1_1Tags = (ID3V1_1Tags)this.mTags[i];
            cOut.println("ID3 V1.1 Tags",oID3V1_1Tags.toString());
            }
         /*.
         ==========================================================================================
         Display the v2.3.0 tags
         ------------------------------------------------------------------------------------------ */
         if (this.mTags[i] instanceof ID3V2_3_0Tags)
            {
            ID3V2_3_0Tags oID3V2_3_0Tags = (ID3V2_3_0Tags)this.mTags[i];
            cOut.println("ID3 V2.3.0 Tags",oID3V2_3_0Tags.toString());
            }
         }
      }



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

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

   @return
      A reference to this object

   @param
      pConsole is a ConsoleStream through which this objects sends its output.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public MP3FileGroomer 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="MP3FileGroomer.java.html#021">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public MP3FileGroomer 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
      CLASS_NAME:
         The name of this class
      DFLT_LINE_LEN:
         The default line length for word wrapping
   ------------------------------------------------------------------------------------------ */
   private static final XString  CLASS_NAME    = new XString(MP3FileGroomer.class.getName());
   private static final int      DFLT_LINE_LEN = ConsoleMessage.defaultLineLength();
   /*.
   ==========================================================================================
   Class variables
      cOut : console output.
   ------------------------------------------------------------------------------------------ */
   private static ConsoleStream  cOut = ConsoleStream.getSingleton();
   /*.
   ==========================================================================================
   This is the base [mp3] file
   ------------------------------------------------------------------------------------------ */
   private XFile  mFile = null;
   /*.
   ==========================================================================================
   This is the MP3File object
   ------------------------------------------------------------------------------------------ */
   private org.blinkenlights.id3.MP3File  mMP3File = null;
   /*.
   ==========================================================================================
   The file's actual current tags are read from the file into this array in no particular
   order.
   ------------------------------------------------------------------------------------------ */
   private ID3Tags[]  mTags = null;
   /*.
   ==========================================================================================
   The file's actual current tags are then transferred from the array to these consolidated
   tag values.
   ------------------------------------------------------------------------------------------ */
   private XString          mArtist       = new XString("");
   private XString          mTitle        = new XString("");
   private XString          mAlbum        = new XString("");
   private XString          mComment      = new XString("");
   private XString          mGenre        = new XString("Other");
   private ID3V1Tags.Genre  mGenreOrdinal = ID3V1Tags.Genre.Other;
   private int              mYear         = 0;
   private XString          mYearString   = new XString("0");
   private int              mTrack        = 255;



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method initializes the object

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

   @param
      pFileName is the name of the MP3 file that this object will manipulate the tags in.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> void initialize(Type pFileName) throws Exception
      {
      this.mFile             = new XFile(pFileName);
      this.mMP3File          = new org.blinkenlights.id3.MP3File(this.mFile);
      this.mTags             = this.mMP3File.getTags();
      this.setTagsFromFile();
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method modifies the file name to conform to rules for Windows and MP3FileGroomer-standard file
   names

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> XString fixedFileName(Type pFileName)
      {
      /*.
      ==========================================================================================
      Use an XString to do the work
      ------------------------------------------------------------------------------------------ */
      XString  xFileName = XString.toXString(pFileName);
      /*.
      ==========================================================================================
      Remove any characters that are invalid in Windows file names
      ------------------------------------------------------------------------------------------ */
      xFileName = xFileName.replace("\\"," ");
      xFileName = xFileName.replace("/", " ");
      xFileName = xFileName.replace(":", " ");
      xFileName = xFileName.replace("*", " ");
      xFileName = xFileName.replace("?", " ");
      xFileName = xFileName.replace("\"","'");
      xFileName = xFileName.replace("<", "(");
      xFileName = xFileName.replace(">", ")");
      xFileName = xFileName.replace("|", " ");
      /*.
      ==========================================================================================
      Delete repeating blanks.
      ------------------------------------------------------------------------------------------ */
      xFileName = xFileName.iteratingReplace("  ", " ");
      /*.
      ==========================================================================================
      Return the name
      ------------------------------------------------------------------------------------------ */
      return xFileName.trim();
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method fixes up a XString by applying basic rules to it

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

   @return
      The XString after having the rules applied

   @param
      pStr is the XString to fix up
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> XString fixedString(Type pStr) throws Exception
      {
      /*.
      ==========================================================================================
      Use an XString to do the work
      ------------------------------------------------------------------------------------------ */
      XString  str = XString.toXString(pStr);
      /*.
      ==========================================================================================
      We don't do no stupid underscores ...
      ------------------------------------------------------------------------------------------ */
      str = str.replace("_", " ");
      /*.
      ==========================================================================================
      Trim leading and trailing white space and return
      ------------------------------------------------------------------------------------------ */
      return str.trim();
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method modifies the artist to conform to rules for standard artists

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private void fixArtist() throws Exception
      {
      /*.
      ==========================================================================================
      Use an NCString to do the work
      ------------------------------------------------------------------------------------------ */
      XString  artist = new NCString(this.mArtist);
      /*.
      ==========================================================================================
      Since the file name contains a '-' that is used to separate the artist from the title, the
      artist and the title strings cannot have a '-' in them because that would through off the
      file name parser that is part of setTitleAndArtistFromFileName().
      ------------------------------------------------------------------------------------------ */
      artist = artist.replace("-", " ");
      /*.
      ==========================================================================================
      If "the" is the first word in the artist name, remove it.
      ------------------------------------------------------------------------------------------ */
      if (artist.startsWith("the "))
         {
         if (artist.length() > 4)
            {
            artist = artist.substring(4);
            }
         }
      /*.
      ==========================================================================================
      Use '&' instead of "and" in artist names
      ------------------------------------------------------------------------------------------ */
      artist = artist.replace(" and " , "&"  );
      artist = artist.replace("&"     , " & ");
      /*.
      ==========================================================================================
      Delete repeating blanks.
      ------------------------------------------------------------------------------------------ */
      artist = artist.iteratingReplace("  ", " ");
      /*.
      ==========================================================================================
      Set the consolidated tags value from the fixed-up XString
      ------------------------------------------------------------------------------------------ */
      this.mArtist = artist.trim();
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method modifies the title to conform to rules for standard titles

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

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private void fixTitle() throws Exception
      {
      /*.
      ==========================================================================================
      Use an NCString to do the work
      ------------------------------------------------------------------------------------------ */
      XString  title = new NCString(this.mTitle);
      /*.
      ==========================================================================================
      Since the file name contains a '-' that is used to separate the artist from the title, the
      artist and the title strings cannot have a '-' in them because that would through off the
      file name parser that is part of setTitleAndArtistFromFileName().
      ------------------------------------------------------------------------------------------ */
      title = title.replace("-", " ");
      /*.
      ==========================================================================================
      Use "and" instead of '&' in titles
      ------------------------------------------------------------------------------------------ */
      title = title.replace(" & " , " And ");
      title = title.replace("&"   , " And ");
      /*.
      ==========================================================================================
      Delete repeating blanks.
      ------------------------------------------------------------------------------------------ */
      title = title.iteratingReplace("  ", " ");
      /*.
      ==========================================================================================
      Set the consolidated tags value from the fixed-up XString
      ------------------------------------------------------------------------------------------ */
      this.mTitle = title.trim();
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method actually renames the file and then reinitializes the instance data that relies on the
   file name.

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

   @return
      True if the file is successfully renamed, false otherwise.

   @param
      pNewName is the new name for the file.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> boolean renameTo(Type pNewName) throws Exception
      {
      /*.
      ==========================================================================================
      First release any other objects that are tying down this file name
      ------------------------------------------------------------------------------------------ */
      this.mMP3File = null;
      /*.
      ==========================================================================================
      Then rename. If it fails, display an error and return.
      ------------------------------------------------------------------------------------------ */
      if ( ! this.mFile.renameTo(pNewName))
         {
         cOut.println
            (
            Const.NOHALT,
            "MP3FileGroomer.renameTo() failed to rename [" +
            this.mFile.getCanonicalPath()                  +
            "]   to   ["                                   +
            pNewName                                       +
            "]"
            );
         /*.
         ==========================================================================================
         Set the MP3File object back to what it was before the rename failure so that if the user
         continues using this MP3FileGroomer object (especially to save the consolidated tags to
         the actual file), a null pointer exception does not get thrown.
         ------------------------------------------------------------------------------------------ */
         this.mMP3File = new org.blinkenlights.id3.MP3File(this.mFile);
         /*.
         ==========================================================================================
         Return false to indicate that the file was not renamed
         ------------------------------------------------------------------------------------------ */
         return false;
         }
      /*.
      ==========================================================================================
      Otherwise it succeeded so reinitialize the instance data that relies upon the file name.
      ------------------------------------------------------------------------------------------ */
      else
         {
         this.mFile    = new XFile(pNewName);
         this.mMP3File = new org.blinkenlights.id3.MP3File(this.mFile);
         return true;
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method applies the character case rules to a XString tag value

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

   @return
      A new XString with the case rules applied

   @param
      pStr is the XString to apply the case rules to
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> XString setCase(Type pStr)
      {
      XString             str        = XString.toXString(pStr);
      XString             delimiters = XString.toXString("()[]{}0123456789.~`!@#$%^&*-_+=|\\:;\"<>,? /");
      ArrayList<XString>  fixedWords = new ArrayList<XString>();
      Iterator<XString>   iterator   = null;
      StringBuffer        buf        = null;
      XString             prev       = null;
      XString             result     = XString.toXString("");
      int                 i          = 0;
      /*.
      ==========================================================================================
      RULE: all letters are lower case unless explicitly capitalized by this method.
      ------------------------------------------------------------------------------------------ */
      str = str.toLowerCase();
      /*.
      ==========================================================================================
      Break the XString up into 2 arrays of strings, one of word delimiters and the other of
      words.
      ------------------------------------------------------------------------------------------ */
      ListOfLists<XString>  wordsAndDelims = str.getWordsAndDelimiters(delimiters);
      ArrayList<XString>             words          = wordsAndDelims.get(0);
      ArrayList<XString>             delims         = wordsAndDelims.get(1);
      /*.
      ==========================================================================================
      For each word apply the case rules
      ------------------------------------------------------------------------------------------ */
      buf      = null;
      iterator = words.iterator();
      while (iterator.hasNext())
         {
         /*.
         ==========================================================================================
         Buf is the current word in StringBuffer form. Prev is the previous word in XString form.
         ------------------------------------------------------------------------------------------ */
         prev = ((buf == null)? null : new NCString(buf.toString()));
         buf  = new StringBuffer(iterator.next().string());
         /*.
         ==========================================================================================
         RULE: the first character of the word should be capitalized
         ------------------------------------------------------------------------------------------ */
         if (buf.length() > 0) buf.setCharAt(0, Character.toUpperCase(buf.charAt(0)));
         /*.
         ==========================================================================================
         RULE: the first letter after these prefixes should be capitalized
         ------------------------------------------------------------------------------------------ */
         buf = this.capAfterPrefix(buf,"Mac");
         buf = this.capAfterPrefix(buf,"Mc");
         buf = this.capAfterPrefix(buf,"O'");
         buf = this.capAfterPrefix(buf,"N'");
         /*.
         ==========================================================================================
         RULE: these words are always all upper case
         ------------------------------------------------------------------------------------------ */
         buf = this.allUpper(buf,"INXS");
         buf = this.allUpper(buf,"ABBA");
         buf = this.allUpper(buf,"CCR");   // Creedence Clearwater Revival
         buf = this.allUpper(buf,"BTO");   // Bachman Turner Overdrive
         buf = this.allUpper(buf,"TV");    // television
         buf = this.allUpper(buf,"FM");    // FM radio
         buf = this.allUpper(buf,"PTA");   // Parent and Teacher's Association
         buf = this.allUpper(buf,"REO");   // REO Speedwagon
         buf = this.allUpper(buf,"REM");   // Rapid Eye Movement
         buf = this.allUpper(buf,"USA");   // Good ol'  U. S. of A.
         buf = this.allUpper(buf,"UB");    // as in UB40
         /*.
         ==========================================================================================
         RULE: these words are always all lower case
         ------------------------------------------------------------------------------------------ */
         buf = this.allLower(buf,"aha");   // a-ha
         /*.
         ==========================================================================================
         RULE: turn "AM" to all caps, but only if the previous word is NOT "I", as in "I Am"
         ------------------------------------------------------------------------------------------ */
         if ((prev != null) && ( ! prev.equals("I")))
            {
            buf = this.allUpper(buf,"AM");   // AM radio
            }
         /*.
         ==========================================================================================
         RULE: these Roman Numerals are always all upper case
         ------------------------------------------------------------------------------------------ */
         buf = this.allUpper(buf,"II");
         buf = this.allUpper(buf,"III");
         buf = this.allUpper(buf,"IV");
         buf = this.allUpper(buf,"VI");
         buf = this.allUpper(buf,"VII");
         buf = this.allUpper(buf,"VIII");
         buf = this.allUpper(buf,"IX");
         buf = this.allUpper(buf,"XX");
         /*.
         ==========================================================================================
         RULE: these words are always all lower case
         ------------------------------------------------------------------------------------------ */
         buf = this.allLower(buf,"vs");
         /*.
         ==========================================================================================
         Add the fixed word to an array
         ------------------------------------------------------------------------------------------ */
         fixedWords.add(XString.toXString(buf));
         }
      /*.
      ==========================================================================================
      Reassemble the delimiter strings and word strings back into a single XString.
      ------------------------------------------------------------------------------------------ */
      for (i=0; i<fixedWords.size(); i++)
         {
         result = result.concat(fixedWords.get(i).concat(delims.get(i)));
         }
      /*.
      ==========================================================================================
      And we're done
      ------------------------------------------------------------------------------------------ */
      return result;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method applies a character case rule to the XString tag value. It capitalizes the first letter
   after a given prefix

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

   @return
      A new XString with the case rule applied

   @param
      pStr is the XString to apply the case rule to
   @param
      pPrefix is a substring which when located at the start of the XString, causes the next character
      after the prefix to be capitalized
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> StringBuffer capAfterPrefix(StringBuffer pBuf, Type pPrefix)
      {
      XString  prefix    = XString.toXString(pPrefix);
      int      bufLen    = pBuf.length();
      int      prefixLen = prefix.length();
      XString  str       = XString.toXString(pBuf);

      if (str.startsWith(prefix))
         {
         if (bufLen > prefixLen)
            {
            pBuf.setCharAt(prefixLen, Character.toUpperCase(pBuf.charAt(prefixLen)));
            }
         }
      return pBuf;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method capitalizes all the characters in a XString buffer if the XString buffer is equal to a
   given XString.

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

   @return
      A new XString with the case rule applied

   @param
      pBuf is the XString buffer to apply the case rule to
   @param
      pStr is the XString that if matched, causes the rule to be applied
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> StringBuffer allUpper(StringBuffer pBuf, Type pStr)
      {
      XString  str = XString.toXString(pBuf);

      if (str.equalsIgnoreCase(pStr))
         {
         str  = str.toUpperCase();
         pBuf = new StringBuffer(str.string());
         }
      return pBuf;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method lowercases all the characters in a XString buffer if the XString buffer is equal to a
   given XString.

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

   @return
      A new XString with the case rule applied

   @param
      pBuf is the XString buffer to apply the case rule to
   @param
      pStr is the XString that if matched, causes the rule to be applied
   *//*
   ---------------------------------------------------------------------------------------------------- */
   private <Type> StringBuffer allLower(StringBuffer pBuf, Type pStr)
      {
      XString  str = XString.toXString(pBuf);

      if (str.equalsIgnoreCase(pStr))
         {
         str  = str.toLowerCase();
         pBuf = new StringBuffer(str.string());
         }
      return pBuf;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  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>
               Java cosmicabyss.com.lib.MP3FileGroomer fileNames
            </DD>
         </DT>
      </DL>

   <P><B>Implementation: </B><A HREF="MP3FileGroomer.java.html#032">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);
      /*.
      ==========================================================================================
      Create an object and send its output to the ConsoleStream
      ------------------------------------------------------------------------------------------ */
      for (int i=0; i<pArgs.length; i++)
         {
         MP3FileGroomer  x = new MP3FileGroomer(pArgs[i]);
         /*.
         ==========================================================================================
         Test code
         ------------------------------------------------------------------------------------------ */
         x.test();
         x.setComment("");

         x.showFileTags();
         x.showTags();
         x.writeTags();
         x.renameFile();
         }
      }



   }  // class MP3FileGroomer



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