package ca.uwaterloo.ece.qhanam.jrsrepair.context;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Properties;
import java.util.Random;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import ca.uwaterloo.ece.qhanam.jrsrepair.AbstractTestExecutor;
import ca.uwaterloo.ece.qhanam.jrsrepair.AntTestExecutor;
import ca.uwaterloo.ece.qhanam.jrsrepair.BashTestExecutor;
import ca.uwaterloo.ece.qhanam.jrsrepair.DocumentASTRewrite;
import ca.uwaterloo.ece.qhanam.jrsrepair.LineCoverage;
import ca.uwaterloo.ece.qhanam.jrsrepair.Statements;
import ca.uwaterloo.ece.qhanam.jrsrepair.Utilities;
import ca.uwaterloo.ece.qhanam.jrsrepair.compiler.JavaJDKCompiler;
/**
* ContextFactory sets up the context for JRSRepair with the createContext
* method. Context contains all the context classes required by JRSRepair.
*
* The input to ContextFactory.buildMutationContext is the Properties object
* from the user-specified properties file.
*
* @author qhanam
*/
public class ContextFactory {
/**
* Builds the context classes for the repair task. The context classes
* are stored in the main context class Context.
*
* @param properties The Properties given as input by the user.
* @return The context for the repair task.
* @throws Exception Throws an exception when a property is missing
* or not formatted properly.
*/
public static Context buildContext(Properties properties) throws Exception {
/* ***
* Set Up Common Structures from Properties
*/
/* Get the random seed to use in our random number generators.
* Default = 1*/
long randomSeed = 1;
if(properties.containsKey("random_seed"))
randomSeed = Integer.parseInt(properties.getProperty("random_seed"));
Random random = new Random(randomSeed);
/* Initialize the scope. This is the lightweight scope check of the two checks. */
HashMap<String, HashSet<String>> scope = new HashMap<String, HashSet<String>>();
/* Build the lists of faulty and seed statements. */
Statements faultyStatements = new Statements(scope, random.nextLong());
Statements seedStatements = new Statements(scope, random.nextLong());
/* Get the path settings for parsing the AST (and resolving bindings) and compiling. */
if(!properties.containsKey("sourcepath")) throw new Exception("Parameter 'sourcepath' not found in properties");
if(!properties.containsKey("classpath")) throw new Exception("Parameter 'classpath' not found in properties");
String[] sourcepaths = unpackArray(properties.getProperty("sourcepath"));
String[] classpaths = unpackArray(properties.getProperty("classpath"));
/* Get the list of source files for us to mutate. */
String[] sourceFilesArray = ContextFactory.getSourceFiles(sourcepaths);
/* Load the source code from the .java files. */
HashMap<String, DocumentASTRewrite> sourceFileContents = ContextFactory.buildSourceDocumentMap(sourceFilesArray);
/* ***
* Set Up Repair Context
*/
RepairContext repair = ContextFactory.buildRepairContext(properties);
/* ***
* Set Up Parser Context
*/
ParserContext parser = ContextFactory.buildParserContext(properties, scope, sourceFileContents, classpaths, sourcepaths, sourceFilesArray, faultyStatements, seedStatements);
/* ***
* Set Up Mutation Context
*/
MutationContext mutation = ContextFactory.buildMutationContext(faultyStatements, seedStatements, sourceFileContents, random);
/* ***
* Set Up Compiler Context
*/
CompilerContext compiler = ContextFactory.buildCompilerContext(properties, classpaths, sourcepaths, sourceFileContents);
/* ***
* Set Up Test Context
*/
TestContext test = ContextFactory.buildTestContext(properties);
/* ***
* Return the context.
*/
return new Context(repair, parser, mutation, compiler, test);
}
/**
* Sets up the RepairContext
* @param properties The user specified properties.
* @return
* @throws Exception Throws an exception if properties are missing or not
* formatted properly.
*/
private static RepairContext buildRepairContext(Properties properties)
throws Exception {
/* Get the settings for mutant generation. */
if(!properties.containsKey("mutation_candidates")) throw new Exception("Parameter 'mutation_candidates' not found in properties");
if(!properties.containsKey("mutation_generations")) throw new Exception("Parameter 'mutation_generations' not found in properties");
if(!properties.containsKey("mutation_attempts")) throw new Exception("Parameter 'mutation_attempts' not found in properties");
int mutationCandidates = Integer.parseInt(properties.getProperty("mutation_candidates"));
int mutationGenerations = Integer.parseInt(properties.getProperty("mutation_generations"));
int mutationAttempts = Integer.parseInt(properties.getProperty("mutation_attempts"));
/* Get the location for the log files and class files. */
if(!properties.containsKey("build_directory")) throw new Exception("Parameter 'build_directory' not found in properties");
File buildDirectory = new File(properties.getProperty("build_directory"));
/* revertFailedCompile is optional (defaults to false). If true, if a compile fails, the last mutation is
* undone before moving on to the next candidate. GenProg and JRSRepair's functionality would have this
* setting false because they build patches BEFORE they execute them. */
boolean revertFailedCompile = false;
if(properties.containsKey("revert_failed_compile")) revertFailedCompile = Boolean.parseBoolean(properties.getProperty("revert_failed_compile"));
/* classdirectories is optional (for multiple output directories) */
String[] classDirectories;
if(properties.containsKey("class_destination_directories")) classDirectories = unpackArray(properties.getProperty("class_destination_directories"));
else classDirectories = new String[] {};
/* nullMutationOnly is optional (for multiple output directories) */
boolean nullMutationOnly = false;
if(properties.containsKey("null_mutation_only")) nullMutationOnly = Boolean.parseBoolean(properties.getProperty("null_mutation_only"));
/* Build a RepairContext object. */
return new RepairContext(mutationCandidates, mutationGenerations, mutationAttempts, buildDirectory, revertFailedCompile, classDirectories, nullMutationOnly);
}
/**
* A factory method that sets up the JDT parser from the details provided
* by the properties file.
* @param properties
* @param scope
* @param sourceFileContents
* @param classpaths
* @param sourcepaths
* @param sourceFilesArray
* @param faultyStatements
* @param seedStatements
* @return
* @throws Exception
*/
private static ParserContext buildParserContext(Properties properties,
HashMap<String, HashSet<String>> scope,
HashMap<String, DocumentASTRewrite> sourceFileContents,
String[] classpaths, String[] sourcepaths,
String[] sourceFilesArray,
Statements faultyStatements, Statements seedStatements) throws Exception {
/* Get the coverage files from fault localization. */
if(!properties.containsKey("faulty_coverage")) throw new Exception("Parameter 'faulty_coverage' not found in properties");
if(!properties.containsKey("seed_coverage")) throw new Exception("Parameter 'seed_coverage' not found in properties");
File faultyCoverageFile = new File(properties.getProperty("faulty_coverage"));
File seedCoverageFile = new File(properties.getProperty("faulty_coverage"));
/* Load the line coverage files. */
LineCoverage faultyLineCoverage = new LineCoverage(faultyCoverageFile);
LineCoverage seedLineCoverage = new LineCoverage(seedCoverageFile);
/* Build a ParserContext object. */
return new ParserContext(scope,
sourceFileContents,
classpaths, sourcepaths,
sourceFilesArray,
faultyLineCoverage, seedLineCoverage,
faultyStatements, seedStatements);
}
/**
* A factory method that builds the appropriate Compiler from the details
* provided by the properties file.
* @param properties
* @param classpaths
* @param sourcepaths
* @param sourceFileContents
* @return
* @throws Exception
*/
private static CompilerContext buildCompilerContext(Properties properties, String[] classpaths, String[] sourcepaths, HashMap<String, DocumentASTRewrite> sourceFileContents) throws Exception {
/* Get the location for the class files. */
if(!properties.containsKey("class_directory")) throw new Exception("Parameter 'class_directory' not found in properties");
String classDirectory = properties.getProperty("class_directory");
/* Get the source folder copy includes and excludes regular expressions. */
String[] copyIncludes = new String[] {};
String[] copyExcludes = new String[] {};
if(properties.containsKey("copy_source_includes")) copyIncludes = unpackArray(properties.getProperty("copy_source_includes"));
if(properties.containsKey("copy_source_excludes")) copyExcludes = unpackArray(properties.getProperty("copy_source_excludes"));
/* Make the compiler we will use. */
JavaJDKCompiler compiler = new JavaJDKCompiler(classDirectory, classpaths, sourceFileContents, sourcepaths, copyIncludes, copyExcludes);
return new CompilerContext(compiler);
}
/**
* A factory method that sets up MutationContext from the details provided
* by the properties file.
* @param properties The user specified properties.
* @param faultyStatements The list of faulty statements.
* @param seedStatements The list of seed statements.
* @param random The random number generator.
* @return
* @throws Exception Throws an exception if properties are missing or not
* formatted properly.
*/
private static MutationContext buildMutationContext(Statements faultyStatements,
Statements seedStatements,
HashMap<String, DocumentASTRewrite> sourceFileContents,
Random random) throws Exception {
/* Build a MutationContext object. */
return new MutationContext(sourceFileContents, faultyStatements, seedStatements, random);
}
/**
* A factory method that builds the appropriate TestExecutor from the details
* provided by the properties file.
* @param properties The properties file provided by the user.
* @return The appropriate concrete instance of AbstractTestExecutor.
* @throws Exception Throws an exception if a parameter in the properties file
* is missing.
*/
private static TestContext buildTestContext(Properties properties) throws Exception {
AbstractTestExecutor testExecutor;
if(!properties.containsKey("test_script")) throw new Exception("Parameter 'test_script' not found in properties");
String testScript = properties.getProperty("test_script");
switch(TestScript.valueOf(testScript)){
case ANT:
if(!properties.containsKey("ant_base_dir")) throw new Exception("Parameter 'ant_base_dir' not found in properties");
if(!properties.containsKey("ant_path")) throw new Exception("Parameter 'ant_path' not found in properties");
if(!properties.containsKey("ant_test_target")) throw new Exception("Parameter 'ant_test_target' not found in properties");
testExecutor = new AntTestExecutor(new File(properties.getProperty("ant_base_dir")),
properties.getProperty("ant_path"),
properties.getProperty("ant_test_target"));
break;
case BASH:
if(!properties.containsKey("bash_script_base_dir")) throw new Exception("Parameter 'bash_script_base_dir' not found in properties");
if(!properties.containsKey("bash_script_path")) throw new Exception("Parameter 'bash_script_path' not found in properties");
testExecutor = new BashTestExecutor(new File(properties.getProperty("bash_script_base_dir")),
properties.getProperty("bash_script_path"));
break;
default:
throw new Exception("Unknown test script type: " + testScript);
}
/* Build a TestContext object. */
return new TestContext(testExecutor);
}
/**
* Options for the type of TestExecutor to build.
* ANT: Apache Ant will execute the junit test cases.
* BASH: A custom Bash shell script will execute the junit test cases.
* @author qhanam
*/
private enum TestScript { ANT, BASH }
/**
* Builds a HashMap with Java file paths as keys and Java file text contents as values.
* @param sourceFilesArray
* @return A HashMap containing the text of the source Java files.
*/
private static HashMap<String, DocumentASTRewrite> buildSourceDocumentMap(String[] sourceFilesArray) throws Exception{
HashMap<String, DocumentASTRewrite> map = new HashMap<String, DocumentASTRewrite>();
for(String sourceFile : sourceFilesArray){
File backingFile = new File(sourceFile);
byte[] encoded = Utilities.readFromFile(backingFile);
IDocument contents = new Document(new String(encoded));
DocumentASTRewrite docrw = new DocumentASTRewrite(contents, backingFile, null);
map.put(sourceFile, docrw);
}
return map;
}
/**
* Generates a list of java source files given a directory, or returns the
* file specified in an array.
* @param sourcePaths The path to the file/directory.
* @return An array of paths to Java source files.
* @throws Exception
*/
private static String[] getSourceFiles(String[] sourcePaths) throws Exception{
Collection<File> sourceFiles = new LinkedList<File>();
String[] sourceFilesArray = null;
for(String sourcePath : sourcePaths){
File sourceFile = new File(sourcePath);
/* If the buggy file is a directory, get all the java files in that directory. */
if(sourceFile.isDirectory()){
sourceFiles.addAll(FileUtils.listFiles(sourceFile, new SuffixFileFilter(".java"), TrueFileFilter.INSTANCE));
}
/* The buggy file may also be a source code file. */
else{
sourceFiles.add(sourceFile);
}
/* Create the String array. */
sourceFilesArray = new String[sourceFiles.size()];
int i = 0;
for(File file : sourceFiles){
sourceFilesArray[i] = file.getCanonicalPath();
i++;
}
}
return sourceFilesArray;
}
/**
* Converts a serialized array in the format "{string1,string2,...,stringN}"
* to a String array.
* @param packed The seralized array.
* @return The String[] array
*/
private static String[] unpackArray(String packed) throws Exception{
String[] unpacked = null;
if(packed.substring(0, 1).equals("{") && packed.substring(packed.length() - 1, packed.length()).equals("}")){
packed = packed.substring(1, packed.length() - 1);
unpacked = packed.split(",");
}
else throw new Exception("Array not enclosed in parenthesis '{ }', cannot unpack.");
return unpacked;
}
}