/**
 * RenameWand 2.2
 * Copyright 2007 Zach Scrivena
 * 2007-12-09
 * zachscrivena@gmail.com
 * http://renamewand.sourceforge.net/
 *
 * RenameWand is a simple command-line utility for renaming files or
 * directories using an intuitive but powerful syntax.
 *
 * TERMS AND CONDITIONS:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package renamewand;

import java.io.Console;
import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.TreeMap;
import java.util.regex.PatternSyntaxException;


/**
 * RenameWand is a simple command-line utility for renaming files or
 * directories using an intuitive but powerful syntax.
 */
public class RenameWand
{
    /**************************************
    * CONSTANTS AND MISCELLANEOUS FIELDS *
    **************************************/

    /** constant: program title */
    private static final String PROGRAM_TITLE =
            "RenameWand 2.2   Copyright 2007 Zach Scrivena   2007-12-09";

    /** constant: special construct separator character */
    private static final char SPECIAL_CONSTRUCT_SEPARATOR_CHAR = '|';

    /** constant: substring range character */
    private static final char SUBSTRING_RANGE_CHAR = ':';

    /** constant: substring delimiter character */
    private static final char SUBSTRING_DELIMITER_CHAR = ',';

    /** constant: integer filter indicator character */
    private static final char INTEGER_FILTER_INDICATOR_CHAR = '@';

    /** constant: regex pattern for register names */
    private static final Pattern REGISTER_NAME_PATTERN = Pattern.compile(
            "[a-zA-Z_][a-zA-Z_0-9]*");

    /**
    * constant: regex pattern for special construct "<length|@expr>" in source pattern string.
    * Match groups: (1,"length"), (2,"@"), (3,"expr")
    */
    private static final Pattern SOURCE_SPECIAL_CONSTRUCT_PATTERN = Pattern.compile(
            "\\<(?:([\\sa-zA-Z_0-9\\." +
            Pattern.quote("+-*/^()[]!" + RenameWand.SUBSTRING_RANGE_CHAR  + RenameWand.SUBSTRING_DELIMITER_CHAR) +
            "]+)" + Pattern.quote(RenameWand.SPECIAL_CONSTRUCT_SEPARATOR_CHAR + "") + ")?(" +
            Pattern.quote(RenameWand.INTEGER_FILTER_INDICATOR_CHAR + "") +
            ")?([\\sa-zA-Z_0-9\\." +
            Pattern.quote("+-*/^()[]!" + RenameWand.SUBSTRING_RANGE_CHAR  + RenameWand.SUBSTRING_DELIMITER_CHAR) +
            "]+)\\>");

    /**
    * constant: regex pattern for special construct "<length|expr>" in target pattern string.
    * Match groups: (1,"length"), (2,"expr")
    */
    private static final Pattern TARGET_SPECIAL_CONSTRUCT_PATTERN = Pattern.compile(
            "\\<(?:([\\sa-zA-Z_0-9\\." +
            Pattern.quote("+-*/^()[]#!@" + RenameWand.SUBSTRING_RANGE_CHAR  + RenameWand.SUBSTRING_DELIMITER_CHAR) +
            "]+)" + Pattern.quote(RenameWand.SPECIAL_CONSTRUCT_SEPARATOR_CHAR + "") +
            ")?([\\sa-zA-Z_0-9\\." +
            Pattern.quote("+-*/^()[]#!@" + RenameWand.SUBSTRING_RANGE_CHAR  + RenameWand.SUBSTRING_DELIMITER_CHAR) +
            "]+)\\>");

    /**
    * constant: regex pattern for a numeric pattern, e.g. -123.45
    * Match groups: (1,"+" or "-"), (2,"123.45")
    */
    private static final Pattern NUMERIC_PATTERN = Pattern.compile(
            "([\\+\\-]?)([0-9]*(?:\\.[0-9]*)?)");

    /** constant: regex pattern for a positive integer pattern, e.g. 42 */
    private static final Pattern POSITIVE_INTEGER_PATTERN = Pattern.compile(
            "\\+?[0-9]+");

    /** operator precedence table for stack evaluation */
    private static final Map<String,Integer> OPERATOR_PRECEDENCE = new TreeMap<String,Integer>();

    /** singular noun for file/directory */
    private static String SINGULAR_NOUN;

    /** plural noun for files/directories */
    private static String PLURAL_NOUN;

    /** standard output */
    static PrintWriter stdout = null;

    /** standard error */
    static PrintWriter stderr = null;

    /** true if this is a Windows OS, false otherwise */
    private static boolean isWindowsOperatingSystem;

    /** current directory (absolute and canonical pathname) */
    static File currentDirectory;

    /** full pathname of the current directory (includes trailing separator) */
    static String currentDirectoryFullPathname;

    /** length of the full pathname of the current directory */
    static int currentDirectoryFullPathnameLength;

    /** register names mapping (register name ---> capture group index) */
    static final Map<String,Integer> registerNames = new TreeMap<String,Integer>();

    /** number of capture groups in source regex pattern */
    private static int numCaptureGroups;

    /** true if the source pattern string is reusable for different files/directories; false otherwise */
    private static boolean sourcePatternIsReusable = false;

    /** subdirectory counter */
    private static int numDirs = 0;

    /*********************
    * RENAME PARAMETERS *
    *********************/

    /** parameter: simulate only; do not actually rename files/directories (default = false) */
    private static boolean simulateOnly = false;

    /** parameter: ignore warnings; do not pause (default = false) */
    private static boolean ignoreWarnings = false;

    /** parameter: recurse into subdirectories (default = false) */
    private static boolean recurseIntoSubdirectories = false;

    /** parameter: automatically rename files/directories without prompting (default = false) */
    private static boolean automaticRename = false;

    /** parameter: match relative pathname, not just the name, of the files/directories (default = false) */
    private static boolean matchRelativePathname = false;

    /** parameter: match lower case name of the files/directories (default = false) */
    private static boolean matchLowerCase = false;

    /** parameter: true if renaming directories; false if renaming files (default = false) */
    private static boolean renameDirectories = false;

    /** parameter: default action on rename operation error (default = '\0') */
    private static char defaultActionOnRenameOperationError = '\0';

    /** parameter: source pattern string */
    private static String sourcePatternString;

    /** parameter: target pattern string */
    private static String targetPatternString;

    /*********************
    * REPORT STATISTICS *
    *********************/

    /** statistic: number of warnings encountered */
    private static int reportNumWarnings = 0;


    /**
    * Main entry point for the RenameWand program.
    *
    * @param args
    *     Command-line argument strings
    */
    public static void main(
            final String[] args)
    {
        /* initialize standard output and error streams */
        final Console console = System.console();

        if (console == null)
        {
            RenameWand.stdout = new PrintWriter(System.out);
            RenameWand.stderr = new PrintWriter(System.err);
        }
        else
        {
            RenameWand.stdout = console.writer();
            RenameWand.stderr = console.writer();
        }

        RenameWand.stdout.print("\n" + RenameWand.PROGRAM_TITLE);
        RenameWand.stdout.flush();

        /* exit status code to be reported to the OS when exiting (default = 0) */
        int exitCode = 0;

        try
        {
            /* determine if this is a Windows OS */
            RenameWand.isWindowsOperatingSystem = System.getProperty("os.name").toUpperCase(Locale.ENGLISH).contains("WINDOWS") &&
                    (File.separatorChar == '\\');

            /* initialize operator precedence table for stack evaluation */
            RenameWand.OPERATOR_PRECEDENCE.put("#",   7);
            RenameWand.OPERATOR_PRECEDENCE.put("#!",  7);
            RenameWand.OPERATOR_PRECEDENCE.put("##",  7);
            RenameWand.OPERATOR_PRECEDENCE.put("##!", 7);
            RenameWand.OPERATOR_PRECEDENCE.put("@",   7);
            RenameWand.OPERATOR_PRECEDENCE.put("@!",  7);
            RenameWand.OPERATOR_PRECEDENCE.put("@@",  7);
            RenameWand.OPERATOR_PRECEDENCE.put("@@!", 7);
            RenameWand.OPERATOR_PRECEDENCE.put("^",   6);
            RenameWand.OPERATOR_PRECEDENCE.put("~",   5); // unary minus (negative) sign
            RenameWand.OPERATOR_PRECEDENCE.put("*",   4);
            RenameWand.OPERATOR_PRECEDENCE.put("/",   4);
            RenameWand.OPERATOR_PRECEDENCE.put("+",   3);
            RenameWand.OPERATOR_PRECEDENCE.put("-",   3);
            RenameWand.OPERATOR_PRECEDENCE.put(RenameWand.SUBSTRING_RANGE_CHAR  + "", 2);
            RenameWand.OPERATOR_PRECEDENCE.put(RenameWand.SUBSTRING_DELIMITER_CHAR + "", 1);

            /* process command-line arguments and configure rename parameters */
            processArguments(args);

            /* nouns for file/directory */
            RenameWand.SINGULAR_NOUN = RenameWand.renameDirectories ? "directory" : "file";
            RenameWand.PLURAL_NOUN = RenameWand.renameDirectories ? "directories" : "files";

            RenameWand.stdout.print("\n\nSource pattern: \"" + RenameWand.sourcePatternString +
                    "\"\nTarget pattern: \"" + RenameWand.targetPatternString +
                    "\"\n\nGetting all candidate " + RenameWand.SINGULAR_NOUN +
                    " names in the current directory" +
                    (RenameWand.recurseIntoSubdirectories ? " recursively..." : "..."));
            RenameWand.stdout.flush();

            /* files/directories to be renamed */
            List<FileUnit> files = null;

            /* get match candidates */
            files = getMatchCandidates();
            final int numMatchCandidates = files.size();

            if (numMatchCandidates == 0)
            {
                RenameWand.stdout.print("\nNo candidate " + RenameWand.SINGULAR_NOUN + " names to match.");
            }
            else
            {
                /* perform source pattern matching on candidate file/directory names */
                RenameWand.stdout.print("\nPerforming source pattern matching on " + numMatchCandidates +
                        " candidate " + RenameWand.SINGULAR_NOUN + " " +
                        ((numMatchCandidates == 1) ? "name" : "names") + "...");
                RenameWand.stdout.flush();

                files = performSourcePatternMatching(files);
                final int numMatched = files.size();

                RenameWand.stdout.print("\n" + numMatched + " out of " + numMatchCandidates +
                        " " + RenameWand.SINGULAR_NOUN + " " +
                        ((numMatched == 1) ? "name" : "names") + " matched.");
                RenameWand.stdout.flush();

                if (numMatched == 0)
                {
                    RenameWand.stdout.print("\nNo " + RenameWand.PLURAL_NOUN + " to rename.");
                }
                else
                {
                    /* evaluate target file/directory names */
                    RenameWand.stdout.print("\nDetermining target " + RenameWand.SINGULAR_NOUN + " " +
                            ((numMatched == 1) ? "name" : "names") + " and renaming sequence...");
                    RenameWand.stdout.flush();

                    evaluateTargetPattern(files.toArray(new FileUnit[numMatched]));

                    /* determine renaming sequence and find clashes, bad names, etc. */
                    final List<RenameFilePair> renameOperations = getRenameOperations(files);
                    final int numRenameOperations = renameOperations.size();

                    /* prompt user before renaming */
                    final boolean proceedToRename = promptUserOnRename(files, numRenameOperations);

                    /* perform rename operations */
                    final int numRenameOperationsPerformed = proceedToRename ? performRenameOperations(renameOperations) : 0;

                    if (!RenameWand.simulateOnly && proceedToRename)
                    {
                        RenameWand.stdout.print("\n\n" + numRenameOperationsPerformed + " out of " +
                                numRenameOperations + " " + RenameWand.SINGULAR_NOUN + " rename " +
                                ((numRenameOperations == 1) ? "operation" : "operations") + " performed.");
                        RenameWand.stdout.flush();
                    }

                    /* report statistics */
                    final StringBuilder report = new StringBuilder();
                    report.append("\n\n" + (RenameWand.renameDirectories ? "DIRECTORY" : "FILE") + " RENAME REPORT");

                    if (RenameWand.reportNumWarnings > 0)
                    {
                        report.append("\n " + RenameWand.reportNumWarnings + " " +
                                ((RenameWand.reportNumWarnings == 1) ? "warning" : "warnings") + " encountered.");
                    }

                    report.append("\n No. of candidate " + RenameWand.SINGULAR_NOUN + " names to match  : " + numMatchCandidates +
                            "\n No. of " + RenameWand.SINGULAR_NOUN + " names matched             : " + numMatched +
                            "\n No. of " + RenameWand.SINGULAR_NOUN + " rename operations required: " + numRenameOperations);

                    if (!RenameWand.simulateOnly && (numRenameOperations > 0))
                    {
                        report.append("\n No. of successful " + RenameWand.SINGULAR_NOUN +
                                " rename operations performed: " + numRenameOperationsPerformed);
                    }

                    RenameWand.stdout.print(report.toString());
                }
            }

            RenameWand.stdout.print("\n\nRenameWand is done!\n\n");
        }
        catch (TerminatingException e)
        {
            /* terminating exception thrown; proceed to abort program */
            /* (this should be the only place where a TerminatingException is caught) */

            exitCode = e.getExitCode();

            if (exitCode != 0)
            {
                /* abnormal termination; print error message */
                RenameWand.stderr.print("\n\nERROR: " + e.getMessage() + "\n");
                RenameWand.stdout.print("\nRenameWand aborted.\n\n");
            }
        }
        catch (Exception e)
        {
            /* catch all other exceptions; proceed to abort program */
            RenameWand.stderr.print("\n\nERROR: An unexpected error has occurred:\n" +
                    getExceptionInformation(e) + "\n");

            exitCode = 1;
            RenameWand.stdout.print("\nRenameWand aborted.\n\n");
        }
        finally
        {
            /* perform clean-up before exiting */
            RenameWand.stderr.flush();
            RenameWand.stdout.flush();
        }

        System.exit(exitCode);
    }


    /**
    * Process command-line arguments.
    *
    * @param args
    *     Command-line argument strings
    */
    private static void processArguments(
            final String[] args)
    {
        final String howHelp = "\nTo display help, run RenameWand without any command-line arguments.";

        /* print usage documentation, if no arguments */
        if (args.length == 0)
        {
            printUsage();
            throw new TerminatingException(null, 0);
        }

        if (args.length < 2)
            throw new TerminatingException("Insufficient arguments:\nThe source and target pattern strings must be specified." + howHelp);

        /* source and target pattern strings */
        RenameWand.sourcePatternString = args[args.length - 2];
        RenameWand.targetPatternString = args[args.length - 1];

        /* check for illegal characters in pattern strings */
        if (RenameWand.sourcePatternString.contains("\0"))
            throw new TerminatingException("Illegal null-character found in source pattern string.");

        if (RenameWand.targetPatternString.contains("\0"))
            throw new TerminatingException("Illegal null-character found in target pattern string.");

        /* default action on rename operation error */
        int skipOnRenameOperationError = 0;
        int undoAllOnRenameOperationError = 0;
        int abortOnRenameOperationError = 0;

        /* process command-line switches */
        for (int i = 0; i < args.length - 2; i++)
        {
            final String sw = args[i];

            if ("--recurse".equals(sw) || "-r".equals(sw))
            {
                /* recurse into subdirectories */
                RenameWand.recurseIntoSubdirectories = true;
            }
            else if ("--dirs".equals(sw) || "-d".equals(sw))
            {
                /* rename directories instead of files */
                RenameWand.renameDirectories = true;
            }
            else if ("--path".equals(sw) || "-p".equals(sw))
            {
                /* match relative pathname, not just the name, of the files/directories */
                RenameWand.matchRelativePathname = true;
            }
            else if ("--lower".equals(sw) || "-l".equals(sw))
            {
                /* match lower case name of the files/directories */
                RenameWand.matchLowerCase = true;
            }
            else if ("--yes".equals(sw) || "-y".equals(sw))
            {
                /* automatically rename files/directories without prompting */
                RenameWand.automaticRename = true;
            }
            else if ("--simulate".equals(sw) || "-s".equals(sw))
            {
                /* simulate only; do not actually rename files/directories */
                RenameWand.simulateOnly = true;
                RenameWand.ignoreWarnings = true;
            }
            else if ("--ignorewarnings".equals(sw) || "-i".equals(sw))
            {
                /* ignore warnings; do not pause */
                RenameWand.ignoreWarnings = true;
            }
            else if ("--skip".equals(sw))
            {
                /* skip on rename operation error */
                skipOnRenameOperationError = 1;
            }
            else if ("--undoall".equals(sw))
            {
                /* undo all on rename operation error */
                undoAllOnRenameOperationError = 1;
            }
            else if ("--abort".equals(sw))
            {
                /* abort on rename operation error */
                abortOnRenameOperationError = 1;
            }
            else
            {
                /* invalid switch */
                throw new TerminatingException("\"" + sw + "\" is not a valid switch." + howHelp);
            }
        }

        if (RenameWand.simulateOnly && RenameWand.automaticRename)
            throw new TerminatingException("Switches --simulate and --yes cannot be used together." + howHelp);

        if (skipOnRenameOperationError + undoAllOnRenameOperationError + abortOnRenameOperationError > 1)
            throw new TerminatingException("Only one of the three switches --skip, --undoall, and --abort may be specified." + howHelp);

        if (RenameWand.simulateOnly && (skipOnRenameOperationError + undoAllOnRenameOperationError + abortOnRenameOperationError > 0))
            throw new TerminatingException("Switches --skip, --undoall, and --abort cannot be used together with --simulate." + howHelp);

        /* default action on rename operation error */
        if (skipOnRenameOperationError > 0)
            RenameWand.defaultActionOnRenameOperationError = 'S';

        if (undoAllOnRenameOperationError > 0)
            RenameWand.defaultActionOnRenameOperationError = 'U';

        if (abortOnRenameOperationError > 0)
            RenameWand.defaultActionOnRenameOperationError = 'A';
    }


    /**
    * Scan current directory to get candidate files/directories for matching.
    *
    * @return
    *     Candidate files/directories for matching
    */
    private static List<FileUnit> getMatchCandidates()
    {
        /* get absolute canonical path of the current directory */
        RenameWand.currentDirectory = new File("");

        try
        {
            RenameWand.currentDirectory = RenameWand.currentDirectory.getCanonicalFile();
        }
        catch (Exception e)
        {
            throw new TerminatingException("Failed to get full pathname of the current directory \"" +
                    RenameWand.currentDirectory.getPath() + "\":\n" + getExceptionInformation(e));
        }

        RenameWand.currentDirectoryFullPathname = RenameWand.currentDirectory.getPath();

        /* include trailing separator */
        if (!RenameWand.currentDirectoryFullPathname.endsWith(File.separator))
            RenameWand.currentDirectoryFullPathname += File.separator;

        RenameWand.currentDirectoryFullPathnameLength = RenameWand.currentDirectoryFullPathname.length();

        /* return value: match candidate files/directories */
        final List<FileUnit> matchCandidates = new ArrayList<FileUnit>();

        /* stack containing the subdirectories to be scanned */
        final Deque<File> subdirectories = new ArrayDeque<File>();
        subdirectories.push(RenameWand.currentDirectory);

        /* reset number of subdirectories scanned */
        RenameWand.numDirs = 0;

        /* perform a DFS scanning of the subdirectories */
        while (!subdirectories.isEmpty())
        {
            RenameWand.numDirs++;

            /* get a directory to be scanned */
            final File dir = subdirectories.pop();
            final File[] listFiles = dir.listFiles();

            if (listFiles == null)
            {
                final String path = dir.getPath();

                reportWarning("Failed to get contents of directory \"" + path +
                        (path.endsWith(File.separator) ? "" : File.separator) +
                        "\".\nThis directory will be ignored.");
            }
            else
            {
                /* subdirectories under this directory */
                final List<File> subdirs = new ArrayList<File>();

                for (File f : listFiles)
                {
                    final boolean isDirectory = f.isDirectory();

                    if (RenameWand.renameDirectories == isDirectory)
                    {
                        final FileUnit u = new FileUnit();
                        u.source = f;
                        u.parentDirId = RenameWand.numDirs;
                        matchCandidates.add(u);
                    }

                    if (isDirectory)
                        subdirs.add(f);
                }

                if (RenameWand.recurseIntoSubdirectories)
                {
                    for (int i = subdirs.size() - 1; i >= 0; i--)
                        subdirectories.push(subdirs.get(i));
                }
            }
        }

        return matchCandidates;
    }


    /**
    * Perform source pattern matching against the names of the candidate
    * files/directories, and return files/directories that match.
    *
    * @param matchCandidates
    *     Candidate files/directories
    * @return
    *     Files/directories with names that match the source pattern
    */
    private static List<FileUnit> performSourcePatternMatching(
            final List<FileUnit> matchCandidates)
    {
        /* return value: files/directories with names that match the source pattern */
        final List<FileUnit> matched = new ArrayList<FileUnit>();

        /* regex pattern used for matching file/directory names */
        Pattern sourcePattern = null;

        /* is the regex pattern matcher reusable for different files/directories? */
        RenameWand.sourcePatternIsReusable = false;

        /* match each candidate file or directory */
        for (FileUnit u : matchCandidates)
        {
            if (!RenameWand.sourcePatternIsReusable)
                sourcePattern = getFileSourcePattern(u);

            /* check if source pattern is successfully generated */
            if (sourcePattern != null)
            {
                /* name string to be matched */
                String name = null;

                if (RenameWand.matchRelativePathname)
                {
                    name = u.source.getPath();

                    if (name.startsWith(RenameWand.currentDirectoryFullPathname))
                        name = name.substring(RenameWand.currentDirectoryFullPathnameLength);
                }
                else
                {
                    name = u.source.getName();
                }

                /* trim off trailing separator */
                while (name.endsWith(File.separator))
                    name = name.substring(0, name.length() - File.separator.length());

                if (RenameWand.matchLowerCase)
                    name = name.toLowerCase(Locale.ENGLISH);

                /* regex pattern matcher */
                final Matcher sourceMatcher = sourcePattern.matcher(name);

                if (sourceMatcher.matches())
                {
                    /* add capture group values to FileUnit's registerValues, and */
                    /* add this file/directory to our list of successful matches  */

                    u.registerValues = new String[RenameWand.numCaptureGroups + 1]; // add index offset 1

                    for (int i = 1; i <= RenameWand.numCaptureGroups; i++)
                        u.registerValues[i] = sourceMatcher.group(i);

                    matched.add(u);
                }
            }
        }

        return matched;
    }


    /**
    * Generate source regex pattern corresponding to the given file/directory.
    *
    * @param u
    *     File/directory for which to generate source regex pattern
    * @return
    *     Source regex pattern corresponding to the given file/directory;
    *     null if regex pattern cannot be generated.
    */
    private static Pattern getFileSourcePattern(
            final FileUnit u)
    {
        /* reset register names and capture group counter */
        RenameWand.registerNames.clear();
        int captureGroupIndex = 0;

        /* assume that source pattern is reusable */
        RenameWand.sourcePatternIsReusable = true;

        /* Stack to keep track of the parser mode: */
        /* "--" : Base mode (first on the stack)   */
        /* "[]" : Square brackets mode "[...]"     */
        /* "{}" : Curly braces mode "{...}"        */
        final Deque<String> parserMode = new ArrayDeque<String>();
        parserMode.push("--"); // base mode

        final int sourcePatternStringLength = RenameWand.sourcePatternString.length();
        int index = 0; // index in sourcePatternString

        /* regex pattern equivalent to sourcePatternString */
        final StringBuilder sourceRegex = new StringBuilder();

        /* parse each character of the source pattern string */
        while (index < sourcePatternStringLength)
        {
            char c = RenameWand.sourcePatternString.charAt(index++);

            if (c == '\\')
            {
                /***********************
                * (1) ESCAPE SEQUENCE *
                ***********************/

                if (index == sourcePatternStringLength)
                {
                    /* no characters left, so treat '\' as literal char */
                    sourceRegex.append(Pattern.quote("\\"));
                }
                else
                {
                    /* read next character */
                    c = RenameWand.sourcePatternString.charAt(index);
                    final String s = c + "";