import java.io.*;
import java.util.*;
import java.util.regex.*;

public class expeller {

  /* File Array - will contain all of the files found to be compared */
  private File[] filesArray;

  /* Directory Array - will contain all of the directories where files are
     found */
  private File[] directoriesArray;

  /* Array used to hold each file in the Linked List for them to be compared
     against each other */
  Object[] filesListToArray;

  /* Holds the directory from which the program was started to know where to
     begin searching */
  String topDir = System.getProperty("user.dir");
  /* The program will need to move through many directories recursively, so
     initially, the curDir will be the topDir */
  String curDir = topDir;

  /* A flag is a complete argument to the program, the '-' and whatever letters
     or words are attached to it */
  String curFlag;
  /* The argument key that is attached to the '-' */
  String curArg;

  /* This string can be changed to represent the current version number of
     Expeller, which will be displayed at runtime */
  final String VERSION_NUMBER = "1.0";

  /* Linked list that contains all of the directories to be searched */
  LinkedList directories = new LinkedList();
  /* Linked list that contains all of the files to be searched */
  LinkedList files = new LinkedList();

  /* Linked List that contains all of the runtime arguments to the program */
  LinkedList argsList = new LinkedList();

  /* One argument that can be given to the program is a filter, so that the
     program will only search for a specific filename or extension, this
     variable holds the filter */
  String fileNameOrTypeFilter = "No filter";
  /* Contains the output filename for the file where the possible cheaters
     will be located. By default, it's cheaters.txt, but the name can also
     be supplied as an argument at runtime */
  String outputFileName = "cheaters.txt";

  /* An argument can be given that tells the program whether to search
     through subdirectories */
  boolean subDirectories;

  /* Argument to supply the percent match threshold for which cheaters will be
     reported */
  int percentMatchThreshold = 75;
  int cheaters = 0;

  /* This is the number of built-in intelligent modules. Intelligent modules
     are deemed intelligent by their complexity and their ability to provide
     a sophisticated comparison of two files */
  final int NUMBER_OF_INTELLIGENT_MODULES = 2;
  /* This is the number of built-in dumb modules. Dumb modules return some
     simple comparison about the file. The two dumb modules currently
     implemented count the lines and character lengths of files */
  final int NUMBER_OF_DUMB_MODULES = 2;

  /* This public method allows Expeller to be run as a stand alone program */
  public static void main(String[] args) {

    expeller Expeller = new expeller(args);
  }

  /* This constructor allows Expeller to be called from another program as a
     stand alone class */
  public expeller (String[] args) {

    /* Program title and version number */
    System.out.println("\nExpeller v" + VERSION_NUMBER);

    /* If there were command line arguments, add them to an array and then
       dissect each one */
    if(args.length != 0) {

      for(int i = 0; i < args.length; i++) {

        argsList.add(args[i]);
      }

      dissectArgs(argsList);
    }

    /* These are informational lines to tell the user which options he/she
       has selected at runtime */
    System.out.println();
    System.out.println("Root search directory: " + curDir);
    System.out.println("Filename or extension filter: " + fileNameOrTypeFilter);
    System.out.println("Search subdirectories: " + subDirectories);
    System.out.println("Output file name: " + outputFileName);
    System.out.println("Percent match threshold: " + percentMatchThreshold);
    System.out.println();

    /* This line displays to let the user know that the program is currently
     loading files */
    System.out.print("Loading files");

    /* This method actually builds the files and directories arrays in order
       to do recursive searching for files */
    growFileAndDirectoryArray();

    /* Copies the elements in the files LinkedList to an Array to compare each
       file against each other */
    filesListToArray = files.toArray();

    /* Output spacing at runtime */
    System.out.println();
    System.out.println();

    /* This line tells the user how many files have been loaded for
       comparison */
    System.out.println("(" + files.size() + " files loaded)" + "\n");

    /* This variable will be used to show the number of comparisons to the
     user */
    int numberOfComparisons = calculateComparisons(files.size());

    /* This line tells the user that file comparisons are currently happening */
    System.out.print("Comparing Files");

    /* This variable is used to hold the time before the comparisons begin
       so that it can be compared to the end time and the time that the
       comparisons took can be calculated */
    long startTime = System.currentTimeMillis();

    /* This method actually handles the comparison of the files */
    compareFiles();

    /* Holds the system time at the end of comparisons to calculate the time it
       took to compare them all */
    long endTime = System.currentTimeMillis();

    /* Calculates how long the comparisons took */
    long secondsForComparisons = (endTime - startTime)/1000;

    /* These lines tell the user how many comparisons took place and how long
       they took */
    System.out.println();
    System.out.println("(" + numberOfComparisons + " comparisons completed in "
                       + secondsForComparisons + " seconds)");
    System.out.println();

    /* This line tells the user how many cheats were found and where the output
       file is located */
    System.out.println(
      "(Output of " + cheaters + " possible Cheats located in " +
      outputFileName + ")");

    System.out.println();
  }

  /* This is a simple method to calculate the number of comparisons with a given
     number of files. The formula used is [(n - 1) + (n - 2) + ... +
     (n - (n - 1)] n = number of files */
  public int calculateComparisons(int numberOfFiles) {

    int numberOfComparisons = 0;

    for(int i = numberOfFiles - 1; i > 0; i--) {

      numberOfComparisons += i;
    }

    return numberOfComparisons;
  }

  /* Simple method that prints the user manual */
  public void printManual() {

    System.out.println("\nUsage: java expeller <options>");
    System.out.println("where possible options include:");
    System.out.println("  -d <full path>               Start search" +
                       " in this directory");
    System.out.println("  -o <output file name>        Output cheaters" +
                       " to this file");
    System.out.println("  -f <filename or extension>   Only search for" +
                       " a specific file\n                               " +
                       "name or all files with a\n                        " +
                       "       specific extension");
    System.out.println("  -s                           Include" +
                       " subdirectories in the search");
    System.out.println("  -t <cheat threshold>         Threshold for" +
                       " considered cheaters");
    System.out.println("  help/?                       This menu");
    System.out.println();
    System.out.println("NOTE: Spaces not supported in arguments");
    System.out.println();
  }

  public void dissectArgs(LinkedList argsList) {

    /* Get the first flag from the argsList */
    curFlag = argsList.getFirst().toString();
    /* Then remove the flag so that we can get the argument next */
    argsList.removeFirst();

    /* If the flag is the help/? flag */
    if (curFlag.equalsIgnoreCase("help")) {

      /* Print the Help Menu */
      printManual();
      System.exit(0);
    }

    /* If the first flag starts correctly, with a "-" */
    else if(curFlag.charAt(0) == '-') {

      /* If it's a directory flag */
      if (curFlag.equalsIgnoreCase("-d")) {

        /* Try getting it off the stack */
        try {

          /* Take the first argument off the stack and make it curArg */
          curArg = argsList.getFirst().toString();
          /* Now remove the first argument on the top of the stack so that next
             time the first argument is pulled, it will be the next argument on
             the stack */
          argsList.removeFirst();
        }

        /* If the flag was thrown and no argument was supplied, catch it */
        catch (Exception e) {

          /* Print out an error letting the user know what's going on */
          System.out.println("\nNo directory argument supplied");
          /* Print the manual so that the user can correct his/her error */
          printManual();
          /* Kill the program so that the user can think about his/her error
             and re-initiate the program with valid arguments */
          System.exit(0);
        }

        /* Temporarily storing the directory object to check if its actually
           a directory */
        File tempCurDir = new File(curArg);

        /* Check if the directory object is actually a directory. If so, make
           the current directory this directory */
        if (tempCurDir.isDirectory())
          curDir = curArg;

          /* If the current argument is not a directory, throw error and exit */
        else {

          System.out.println("\nERROR: Directory does not exist.");
          printManual();
          System.exit(0);
        }
      }

      /* If the flag is a directory flag */
      else if (curFlag.equalsIgnoreCase("-f")) {

        try {

          curArg = argsList.getFirst().toString();
          argsList.removeFirst();
        }

        catch (Exception e) {

          System.out.println("\nNo file argument supplied");
          printManual();
          System.exit(0);
        }

        /* Pull the current argument off the stack and make it the
           fileNameOrType variable so that we can use it to search files */
        fileNameOrTypeFilter = curArg;
      }

      /* If the flag is the outputFile flag */
      else if (curFlag.equalsIgnoreCase("-o")) {

        try {

          curArg = argsList.getFirst().toString();
          argsList.removeFirst();
        }

        catch (Exception e) {

          System.out.println("\nNo file argument supplied");
          printManual();
          System.exit(0);
        }

        /* Pull the current argument off the stack and make it the
           outputFileName variable so that we can use it to output Cheaters */
        outputFileName = curArg;
      }

      /* If the flag is the percentMatchThreshold flag */
      else if (curFlag.equalsIgnoreCase("-t")) {

        try {

          curArg = argsList.getFirst().toString();
          argsList.removeFirst();
        }

        catch (Exception e) {

          System.out.println("\nNo file argument supplied");
          printManual();
          System.exit(0);
        }

        /* Pull the current argument off the stack and make it the
           percentMatchThreshold variable so that we can use it to output
           only files with percentMatch at or above this threshold */
        percentMatchThreshold = Integer.valueOf(curArg).intValue();
      }

      /* If the flag is a subDirectory flag */
      else if (curFlag.equalsIgnoreCase("-s")) {

        /* Say that we are searching subDirectories */
        subDirectories = true;
      }

      /* If it's not any of these, it's an invalid flag. Print the manual,
         throw an error, and quit. */
      else {

        System.out.println("\nERROR: Invalid flag supplied.");
        printManual();
        System.exit(0);
      }

      /* If there are more arguments in the list, recursively call this
         method and parse them as well */
      if (argsList.size() != 0) {

        dissectArgs(argsList);
      }
    }

    /* If the flag does not start with a "-", throw an error, print the manual
       and quit */
    else {

      System.out.println("\nERROR: Invalid flag supplied. Flags must start" +
                         " with a \"-\".");
      printManual();
      System.exit(0);
    }
  }

  /* This method recursively adds files and directories to their respective
     arrays. The recursion comes in when sub directories exist */
  public void growFileAndDirectoryArray() {

    /* Creates a new file object that is actually the directory from which the
       program is run.*/
    File file = new File(curDir);

    /* Creates a new fileFilter object to filter only files with the user
       specified extension */
    FileFilter fileFilter = new FileFilter() {

      public boolean accept(File file) {

        if(fileNameOrTypeFilter.equals("No filter"))
           fileNameOrTypeFilter = "";

        /* If this file object is not a directory (is a file) and its extension
           is the user specified extension, then return true to add it to
           the file array, else return false and don't add it to the array */
        if (!file.isDirectory() && file.getName().endsWith(fileNameOrTypeFilter)
            && file.isFile()) {

          return true;
        }

        else {

          return false;
        }
      }
    };

    /* Creates a new directoryFilter object to filter only directories */
    FileFilter directoryFilter = new FileFilter() {

      public boolean accept(File file) {

        /* If this file object is a directory then return true to add it to
          the directory array, else return false and don't add it to the
          array */
        if (file.isDirectory()) {

          return true;
        }

        else {

          return false;
        }
      }
    };

    /* Puts all of the files that comply with the filter in a file array */
    filesArray = file.listFiles(fileFilter);

    /* Puts all of the directories that comply with the filter in a directory
       array */
    directoriesArray = file.listFiles(directoryFilter);

    /* Try here because some directories may be hidden or we may not have
       permission to view them */
    try {

      /* Copies all file locations in the array to the linked list */
      for (int i = 0; i < filesArray.length; i++) {

        /* For every file that is found in curDir, print a "." */
        System.out.print(".");
        /* Add every file that is found to the filesArray */
        files.add(filesArray[i]);
      }

      /* Copies all directory locations in the array to the linked list */
      for (int i = 0; i < directoriesArray.length; i++) {

        directories.add(directoriesArray[i]);
      }
    }

    /* Catch the exception that a directory is hidden or not accessible
       because of permissions */
    catch(Exception e) {

      System.out.println("\nDirectory not accessible: " + curDir);
      System.out.println();
    }

    /* If there are any directories in the linked list, we're going to go
       into them and search for more files and directories */
    if(directories.size() != 0) {

        /* Set the current directory to the last directory on the linked list
           so that we can search inside it by calling this method recursively*/
        curDir = directories.getLast().toString();
        /* Take the now curDir off the list so that we don't search it again */
        directories.removeLast();
        /* Call the current method recursively so that we can search the new
           curDir for files and directories */
        if(subDirectories == true)
          growFileAndDirectoryArray();
    }
  }

  /* This method will take the files list array and compare each file with
     each other */
  public void compareFiles() {

    /* This number will keep track of the matching percentage of each pair of
       files */
    int percentMatch = 0;
    /* This variable is only used to tell the programmer the percentMatch
       for each module for debugging
       The tempPercentMatch outputs will be left in the program (but commented
       out) for easy debugging purposes upon addition of new modules */
    int tempPercentMatch = 0;

    /* Here we calculate the number of modules we currently have installed
       DUMB MODULES are divided in half because their percent match is not
       worth as much as an intelligent module, in fact, it's worth about half */
    int numberOfModulesDenominator =
        NUMBER_OF_INTELLIGENT_MODULES + (NUMBER_OF_DUMB_MODULES/2);

    /* We must put this section in a try/catch statement because we will be
       writing to a file, which throws exceptions */
    try {

      /* Set up the output stream for the file to be written to
         outputFileName will be cheaters.txt by default, unless otherwise
         specified by the user at runtime as an argument */
      FileWriter outStream = new FileWriter(outputFileName);
      BufferedWriter outputFile = new BufferedWriter(outStream);
      PrintWriter outFile = new PrintWriter(outputFile);

      /* Begin output to cheaters.txt */
      outFile.println();
      outFile.println("Files to look at:");

      /* These two loops will traverse the array and compare every two files */
      for (int x = 0; x < filesListToArray.length - 1; x++) {

        for (int y = (x + 1); y < filesListToArray.length; y++) {

          /* Reset the percentMatch and tempPercentMatch for this pair */
          percentMatch = 0;
          tempPercentMatch = 0;

          /* Create two files from the two that we are currently looking at in
             the array */
          File file1 = new File(filesListToArray[x].toString());
          File file2 = new File(filesListToArray[y].toString());

          /* Prints the two files to be compared on every iteration
             Left in for future debugging */
          //System.out.println("\n" + file1.getName() + " " + file2.getName());

          /* Run the modules, one-by-one, each one adding to the total
             percentMatch */
          tempPercentMatch = modCompareLength(file1, file2);
          percentMatch += tempPercentMatch*0.5;
          //System.out.println("compare length percent match: " +
                             //tempPercentMatch);

          tempPercentMatch = modCompareNumberOfLines(file1, file2);
          percentMatch += tempPercentMatch*0.5;
          //System.out.println("compare lines percent match: " +
              //tempPercentMatch);

          tempPercentMatch = modCountTokens(file1, file2);
          percentMatch += tempPercentMatch;
          //System.out.println("count tokens percent match: " +
              //tempPercentMatch);

          tempPercentMatch = modCountBlockSize(file1, file2);
          /* We need to have another variable for this percent match because
             if the file comes back dirty (not a real compilable code file) the
             percent match will be thrown out */
          int modCountBlockSizePercentMatch = tempPercentMatch;

          /* If the module's percent match is not thrown out for these two
             files, add the tempPercentMatch to the percentMatch total */
          if(modCountBlockSizePercentMatch != -1)
            percentMatch += tempPercentMatch;

          //System.out.println("count block size percent match: " +
                             //tempPercentMatch);

          /* If the modCountBlockSizePercentMatch is valid, we'll need to divide
             the total percent match by it to get a 0-100 average, otherwise
             divide by one less to represent the missing modules percentMatch */
          if(modCountBlockSizePercentMatch != -1)
            percentMatch /= numberOfModulesDenominator;
          else
            percentMatch /= (numberOfModulesDenominator - 1);

          //System.out.println("Total FILE percent match: " + percentMatch);

          /* If the two files' percentMatch is greater than the percentMatch
             threshold, print them out to the screen */
          if (percentMatch >= percentMatchThreshold) {

            /* These lines will print to the screen any two files that have
               a percent match that is the same or higher than the threshold
               Left in for future debugging */
            //System.out.print(filesListToArray[x].toString() + "  " +
                             //filesListToArray[y].toString() + "  ");
            //System.out.println(percentMatch);

            /* These lines write out to file every two files which match
               is at or above the threshold */
            outFile.println();
            outFile.println("  Cheat Possibility: " + percentMatch + "%");
            outFile.println();
            outFile.println("    " + filesListToArray[x].toString());
            outFile.println("    " + filesListToArray[y].toString());
            outFile.println();

            /* Increment the cheaters value for output at the end of the file
               and on the screen to tell the user how many cheaters we found */
            cheaters++;
          }

          /* For every comparison made, output a '.' to the screen to let the
             user know that the system is working */
          System.out.print(".");
        }
      }

      /* Print total number of possible cheats to file */
      outFile.println();
      outFile.println(cheaters + " possible cheats");
      outFile.println();

      /* Close the output file, which will in turn write out all data in the
         input stream */
      outFile.close();
    }

    catch (IOException exception) {

      System.out.println(exception);
    }
  }

  /* This method will return the file as a text string so that it can be
     manipulated and checked. The deleteComments option indicates whether you
     want comments taken out of the file (it's better to compare two files
     without comments since anything can be put in comments to throw comparisons
     off */
  public String getFileAsString(File file, boolean deleteComments) {

    /* This variable holds the complete file in a string as it's read from the
       file itself */
    String fileAsString = "";
    /* Holds each line of the file as it's read in, and then added to the
       fileAsString */
    String currentLine = "";

    /* This variable exists to tell Expeller if the current line is within a
       commented segment (the last line ended without closing the commented
       section */
    boolean inComment = false;

    try {

      FileInputStream fileInputStream = new FileInputStream(file);

      InputStreamReader fileInputStreamReader = new InputStreamReader(
          fileInputStream);

      BufferedReader fileBufferedReader = new BufferedReader(
          fileInputStreamReader);

        /* While there are lines to be read */
        while(fileBufferedReader.ready()) {

        currentLine = fileBufferedReader.readLine();

        /* If we are currently in a comment and the comment doesn't end on this
           line (there are no end commend delimiters in this line), continue
           the search and this line will be omitted */
        if(inComment == true && (currentLine.indexOf("*/") == -1)) {

          continue;
        }

        /* The comment must have ended so set the inComment flag to false */
        inComment = false;

        /* We don't care about leading or trailing whitespace, so we trim the
           line */
        currentLine = currentLine.trim();

        /* In this section, we will take the comments out of the file */
        if(deleteComments == true) {

          /* Here we have a regular expression that should catch all comments
             in the file and replace them with a blank line ("") */
          currentLine = currentLine.replaceAll(
              "(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "");

          /* If we have comments that are not completely contained on one line
             (such as this comment), we have to signal that we are inside a
             comment with the inComment flag. If the comment doesn't start at
             the beginning of the line, we still want the text from position 0
             to where the comment begins */
          if (currentLine.indexOf("/*") != -1) {

            currentLine = currentLine.substring(0, currentLine.indexOf( ("/*"))).
                trim();

            inComment = true;
          }

          /* If the current line does contain an end comment delimiter, we'll
             want the text after the delimiter */
          if (currentLine.indexOf("*/") != -1)
            currentLine = currentLine.substring(currentLine.indexOf("*/") + 2,
                                                currentLine.length());
        }

        /* In the case that a fully commented line with
           [asterik][star] text [asterik][star] or [slash][slash] is found, we
           will replace it with a blank line, and if so, we don't want to add it
           to the fileAsString because whitespace doesn't count */
        if(currentLine.length() > 0)
          fileAsString += currentLine + "\n";
      }
    }

    catch (Exception e){
        e.printStackTrace();
    }

    return fileAsString;
  }

  /* This method is used to get the depth of the file pertaining to curly braces
     any information completely outside of curly braces is in depth0, anything
     inside the class call is depth1, and so on. Knowing this depth will be
     helpful in determining how big our depth comparison array should be */
  public int getDepthOfFile(File file) {

    /* This is the depth of any given succession of opening curly braces */
    int depth = 0;
    /* This is the deepest depth found so far */
    int deepestDepth = 0;

    /* Here we're getting the file as a string and deleting comments from it */
    String fileAsString = getFileAsString(file, true);

    /* We are converting the file string to a character array so that we can
       search through it for the opening curly brace. We use this way rather
       than the StringTokenizer way due to StringTokenizer's lack of ability to
       search for multiple tokens */
    char[] fileAsCharArray = fileAsString.toCharArray();

    /* In this for loop we search for open braces, and for every one, add one to
       the depth. If the depth is greater than the deepest depth, we make the
       two equal. We also subtract one from the current depth if a closing curly
       brace is found to show that we are leaving a block */
    for(int i = 0; i < fileAsCharArray.length; i ++) {

      if(fileAsCharArray[i] == '{') {

        depth++;

        if(depth > deepestDepth)
          deepestDepth = depth;
      }

      if(fileAsCharArray[i] == '}') {

        depth--;
      }
    }

    return deepestDepth;
  }

  /* Simple method that returns the greater of two numbers. It is used to build
     arrays when there are two elements involved (since an array cannot grow) */
  public int greaterOfTwo(int int1, int int2) {

    if(int1 > int2)
      return int1;
    else
      return int2;
  }

  /* This dumb module will compare the lengths of two files */
  public int modCompareLength(File file1, File file2) {

    /* Percent match for this module and these two files */
    int percentMatchThisModule = 0;

    int file1Length = (int)file1.length();
    int file2Length = (int)file2.length();

    percentMatchThisModule = percentMatch(file1Length, file2Length);

    return percentMatchThisModule;
  }

  /* This dumb module will compare the number of lines in two files */
  public int modCompareNumberOfLines(File file1, File file2) {

    int percentMatchThisModule = 0;

    int file1NumberOfLines = 0;
    int file2NumberOfLines = 0;

    String file1AsString = getFileAsString(file1, true);
    String file2AsString = getFileAsString(file2, true);

    /* We simply take the files as strings and delimit them by the end of line
       character in order to count the number of lines */
    StringTokenizer file1AsStringTokenizer = new StringTokenizer(file1AsString, "\n");
    StringTokenizer file2AsStringTokenizer = new StringTokenizer(file2AsString, "\n");

    file1NumberOfLines = file1AsStringTokenizer.countTokens();
    file2NumberOfLines = file2AsStringTokenizer.countTokens();

    percentMatchThisModule = percentMatch(file1NumberOfLines, file2NumberOfLines);

    return percentMatchThisModule;
  }

  /* This module is an 'intelligent' module. It counts the number of lines
     within any given block */
  public int modCountBlockSize(File file1, File file2) {

    /* Here we build arrays to hold the size of each block in a file. We add
       one to the size to account for text outside of any braces (depth0) */
    int file1BlockSizes[] = new int[getDepthOfFile(file1) + 1];
    int file2BlockSizes[] = new int[getDepthOfFile(file2) + 1];
    /* Array to hold the percent matches for each depth's lines. It needs to be
       the greater size of the two depths */
    int percentMatches[] =
        new int[greaterOfTwo(file1BlockSizes.length, file2BlockSizes.length)];

    /* Used to hold the depth that we are currently in */
    int blockCounter = 0;
    /* Holds the percent match so far and the total percent match for this
       module */
    int percentMatchesTotal = 0;
    int percentMatchThisModule = 0;

    /* Used to hold the current line being looked at from the file */
    String file1CurrentLine;
    String file2CurrentLine;

    /* Both files as strings */
    String file1AsString = getFileAsString(file1, true);
    String file2AsString = getFileAsString(file2, true);

    /* We delimit each fileAsString by the \n symbol to get one line at a
       time */
    StringTokenizer file1AsStringTokenizer =
        new StringTokenizer(file1AsString, "\n");
    StringTokenizer file2AsStringTokenizer =
        new StringTokenizer(file2AsString, "\n");

    /* Constant loop to search through the file */
    while(true) {

      /* Try to get the next line */
      try {
        file1CurrentLine = file1AsStringTokenizer.nextToken();
      }

      /* If the file has no (or in some systems no) lines, we'll need to abort
         this comparison */
      catch(NoSuchElementException e) {

        break;
      }

      /* Here we're gong to check if this line contains the beginning of a
         block (has opening curly braces) */
      if (file1CurrentLine.indexOf("{") != -1 &&
          file1CurrentLine.indexOf("}") == -1) {

        /* If so, increment the block counter to show that we have entered
           a deeper depth */
        blockCounter++;
        /* Add one to this depth's location in the fileBlockSizes array to show
           that this depth contains another line */
        file1BlockSizes[blockCounter]++;
        continue;
      }

      /* If we are leaving a block */
      if (file1CurrentLine.indexOf("}") != -1 &&
          file1CurrentLine.indexOf("{") == -1) {

        /* decrement the block counter */
        blockCounter--;

        /* If the block counter is less than zero, that means there were curly
           braces that don't close out a block (invalid file), so we return a
           -1 to indicate that this module's percent match should not be used
           in the total comparison */
        if(blockCounter < 0)
          return -1;

        /* Even tho this line is the end of a block, it still counts, so add
           one to it's location in the BlockSizes array */
        file1BlockSizes[blockCounter]++;
        continue;
      }

      /* This catches all lines that have an opening and closing braces, such
         as a very short method. We don't add to the blockCounter here because
         it would then be able to fool Expeller by putting each of your methods
         in a single line. We just count these lines as part of the block that
         we are currently in */
      if ((file1CurrentLine.indexOf("}") == -1 &&
           file1CurrentLine.indexOf("{") == -1)
          || (file1CurrentLine.indexOf("}") != -1 &&
              file1CurrentLine.indexOf("{") != -1)) {

        file1BlockSizes[blockCounter]++;
        continue;
      }
    }

    /* Reset the block counter for file 2 and do the same thing to it */
    blockCounter = 0;

    while(true) {

      try {
        file2CurrentLine = file2AsStringTokenizer.nextToken();
      }

      catch(NoSuchElementException e) {

        break;
      }

      if (file2CurrentLine.indexOf("{") != -1 && file2CurrentLine.indexOf("}") == -1) {

        blockCounter++;
        file2BlockSizes[blockCounter]++;
        continue;
      }

      if (file2CurrentLine.indexOf("}") != -1 && file2CurrentLine.indexOf("{") == -1) {

        blockCounter--;

        if(blockCounter < 0)
          return -1;

        file2BlockSizes[blockCounter]++;
        continue;
      }

      if ((file2CurrentLine.indexOf("}") == -1 && file2CurrentLine.indexOf("{") == -1)
          || (file2CurrentLine.indexOf("}") != -1 && file2CurrentLine.indexOf("{") != -1)) {

        file2BlockSizes[blockCounter]++;
        continue;
      }
    }

    /* Here we will compute percent matches for all of the methods' lengths and
       put our results in a percentMatches array */
    for(int i = 0; i < percentMatches.length; i++) {

      try {
        percentMatches[i] = percentMatch(file1BlockSizes[i], file2BlockSizes[i]);
      }
      catch(ArrayIndexOutOfBoundsException e) {

        /* If a certain file has a depth X, and another file has the depth Y,
           the percentMatch array will have length equal to the 'deeper' file.
           The more shallow file will not have array locations for the deeper
           depths, so accessing those locations will throw an exception. We know
           why this exception is thrown (the depth doesn't exist), so we fill
           those percent matches with 0, because a non-existent depth compared
           with any depth other than non-existent (which isn't even a valid
           comparison because the percentMatches length is always as long as the
           deepest depth file), should be 0 */
        while(i < percentMatches.length) {

          percentMatches[i] = 0;
          i++;
        }

        break;
      }
    }

    /* Add up all of the depths percent matches for a total percent match of
       this module */
    for(int j = 0; j < percentMatches.length; j++) {

      percentMatchesTotal += percentMatches[j];
    }

    /* We'll need to divide the modules percent match by the number of percent
       matches in order to get a 0-100 scale */
    percentMatchThisModule = percentMatchesTotal / percentMatches.length;

    return percentMatchThisModule;
  }

  /* This module counts Regular Expressions as Tokens and compares them to
     how many times they were used in another file. */
  public int modCountTokens(File file1, File file2) {

    int numberOfRegularExpressions = 19;

    /* This string, along with the entries below, will be all of the regular
       expressions searched for */
    String expressionsToSearchFor[] = new String[numberOfRegularExpressions];

    expressionsToSearchFor[0] = "(for)( *)(\\()";
    expressionsToSearchFor[1] = "(while)( *)(\\()";
    expressionsToSearchFor[2] = "(if)( *)(\\()";
    expressionsToSearchFor[3] = "(else)( *)(if)";
    expressionsToSearchFor[4] = "(else)( *)(\\{)";
    expressionsToSearchFor[5] = "(System.out.println)";
    expressionsToSearchFor[6] = "(break)( *)(;)";
    expressionsToSearchFor[7] = "(continue)( *)(;)";
    expressionsToSearchFor[8] = "(==)";
    expressionsToSearchFor[9] = "(\\+)(\\+)";
    expressionsToSearchFor[10] = "(\\-)(\\-)";
    expressionsToSearchFor[11] = "(int)( *)[a-zA-Z][a-zA-Z0-9]*";
    expressionsToSearchFor[12] = "(\\);)";
    expressionsToSearchFor[13] = "(.)";
    expressionsToSearchFor[14] = "[a-zA-Z][a-zA-Z0-9]*\\(\\);";
    expressionsToSearchFor[15] = "(!)";
    expressionsToSearchFor[16] = "(\\{)";
    expressionsToSearchFor[17] = "(\\})";
    expressionsToSearchFor[18] = "(||)";

    int percentMatchThisModule = 0;
    /* Keeps track of the percent match between the two files for any given
       iteration of the search loop */
    int percentMatchThisRegularExpression;

    /* In these arrays, we'll store the number of each expression */
    int expressionsInFile1[] = new int[numberOfRegularExpressions];
    int expressionsInFile2[] = new int[numberOfRegularExpressions];

    /* These are the required class variables to perform string matching with
       regular expressions */
    Pattern pattern;
    Matcher matcherFile1;
    Matcher matcherFile2;

    for(int i = 0; i < numberOfRegularExpressions; i++) {

      percentMatchThisRegularExpression = 0;

      /* We compile a pattern from each regular expression that we are searching
         for */
      pattern = Pattern.compile(expressionsToSearchFor[i]);

      /* Matcher's created with each file matched against the regular expression
         of this iteration */
      matcherFile1 = pattern.matcher(getFileAsString(file1, true));
      matcherFile2 = pattern.matcher(getFileAsString(file1, true));

      /* While finding this pattern in each of these two files, add 1 to the
         location in each of their 'found' array */
      while(matcherFile1.find())
        expressionsInFile1[i]++;

      while(matcherFile2.find())
        expressionsInFile2[i]++;

      /* We add this iterations percent match to the total percent match for
         this module */
      percentMatchThisRegularExpression =
          percentMatch(expressionsInFile1[i],expressionsInFile2[i]);
      percentMatchThisModule += percentMatchThisRegularExpression;
    }

    /* We divide the percentMatchThisModule by the number of Regular Expressions
       and we get a percentMatch on a scale of 1-100 */
    percentMatchThisModule /= numberOfRegularExpressions;

    /* We return the percentMatchThisModule to be included in the total percent
       match */
    return percentMatchThisModule;
  }

  /* This is the method that computes the percent match of two numbers. The
     equation that is used is 100 - percent difference (which is
     difference/average) */
  public int percentMatch(int number1, int number2) {

    int difference = number1 - number2;
    int sum = number1 + number2;

    /* If the two numbers added together are equal to 0 (if both numbers are 0
       because we won't ever have negative numbers), return a 100 percent match
       because non-existence is just as important as existence */
    if(sum == 0)
      return 100;

    /* If one number b is greater than number a, we'll end up with a negative
       difference, so reverse the sign */
    if(difference < 0)
      difference *= -1;

    /* We set up our equation */
    int numerator = difference;
    double denominator = (sum/2.0);
    double equation = numerator/denominator;

    /* We actually just calculated percent difference, so to get percent match
       we'll need to subtract percent difference from 100. We cast this as an
       int because it's possible for the denominator to be a decimal number */
    int percentMatch = (int)(100 - (equation*100));

    /* If the percent match is greater than 0 (the percent difference is less
       than 100), return the percent match */
    if(percentMatch > 0)
      return percentMatch;
    /* If the percent match is less than or equal to zero (the percent
       difference is greater than 100), return 0 */
    else
      return 0;
  }
}
