Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ benchmarking/datasets/
benchmarking/outputs/
benchmarking/sources/
benchmarking/compiled_classes/
searchResults/

# Ignore IDE specific files
.vscode
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ dependencies {
runtimeOnly 'org.apache.logging.log4j:log4j-core'
// Log4j JSON-encoding support
runtimeOnly 'org.apache.logging.log4j:log4j-layout-template-json'

// Argument parsing library
implementation 'info.picocli:picocli:4.7.7'
}

checkerFramework {
Expand Down
65 changes: 44 additions & 21 deletions src/main/java/RefactoringEngine.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.logging.log4j.Logger;
Expand All @@ -19,32 +17,18 @@
*/
public class RefactoringEngine {
private static final Logger LOGGER = LogManager.getLogger();
private static final Logger SEARCH_LOGGER = LogManager.getLogger("SearchLogger");

/**
* List of refactorings to apply
*/
private final List<Refactoring> refactorings;

public RefactoringEngine(List<String> refactoringNames) {
refactorings = new ArrayList<>();

for (String name : refactoringNames) {
switch (name) {
case AddNullCheckBeforeDereferenceRefactoring.NAME ->
refactorings.add(new AddNullCheckBeforeDereferenceRefactoring());
case BooleanFlagRefactoring.NAME -> refactorings.add(new BooleanFlagRefactoring());
case SentinelRefactoring.NAME -> refactorings.add(new SentinelRefactoring());
case NestedNullRefactoring.NAME -> refactorings.add(new NestedNullRefactoring());
case "All" -> refactorings.addAll(Arrays.asList(new AddNullCheckBeforeDereferenceRefactoring(),
new BooleanFlagRefactoring(), new SentinelRefactoring(), new NestedNullRefactoring()));
default -> System.err.println("Unknown refactoring: " + name);
}

if (refactorings.isEmpty()) {
System.err.println("No valid refactorings specified. Exiting.");
System.exit(1);
}
public RefactoringEngine(List<Refactoring> refactorings) {
if (refactorings.isEmpty()) {
throw new IllegalArgumentException("No valid refactorings specified");
}
this.refactorings = refactorings;
}

/**
Expand Down Expand Up @@ -85,4 +69,43 @@ public void preVisit(ASTNode node) {
return document.get();
}

/**
* Searches a given source file for applicable refactorings in
* {@value refactorings} Search results are logged to a CSV file via a dedicated
* logger ("SearchLogger")
*
* @param cu
* The compilation unit to use
* @param sourceCode
* A string containing the source code of the file to search through
* @param filePath
* A string containing the patth of the file to search through
* @param fileName
* A string containing the name of the file to search through
*/
public void searchRefactorings(CompilationUnit cu, String sourceCode, String filePath, String fileName) {
Document document = new Document(sourceCode);

for (Refactoring refactoring : refactorings) {
cu.accept(new ASTVisitor() {
@Override
public void preVisit(ASTNode node) {
LOGGER.debug("Visiting AST Node {}", node);

if (!refactoring.isApplicable(node)) {
return;
}

String refactoringModule = refactoring.getClass().getSimpleName();
int lineNumber = cu.getLineNumber(node.getStartPosition());
String nodeClass = node.getClass().getSimpleName();
// Escape all new lines and quote the code snippet to preserve csv formatting
String codeSnippet = "\"" + node.toString().replace("\"", "\"\"") + "\"";

SEARCH_LOGGER.info("{},{},{},{},{},{}", refactoringModule, filePath, fileName, lineNumber,
nodeClass, codeSnippet);
}
});
}
}
}
111 changes: 82 additions & 29 deletions src/main/java/VGRTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -20,45 +20,65 @@
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;

import picocli.CommandLine;
import picocli.CommandLine.*;

/**
* Program entrypoint class; Runs the refactoring engine on given source code
*/
public class VGRTool {
@Command(name = "VGRTool", mixinStandardHelpOptions = true, version = "0.1", description = "Program entrypoint class; Runs the refactoring engine on given source code")
public class VGRTool implements Runnable {

private static final Logger LOGGER = LogManager.getLogger();

// List of valid refactoring module names for Picocli
static class ValidRefactoringModules extends ArrayList<String> {
ValidRefactoringModules() {
super(List.of(AddNullCheckBeforeDereferenceRefactoring.NAME, BooleanFlagRefactoring.NAME,
SentinelRefactoring.NAME, NestedNullRefactoring.NAME, "All"));
}
}

// Picocli automatically assigns values to arguments during runtime,
// guaranteeing initialization
@SuppressWarnings("initialization.field.uninitialized")
// First position argument represents target directory
@Parameters(index = "0", description = "Path of directory to execute program on")
private String targetDir;

// Picocli automatically assigns values to arguments during runtime,
// guaranteeing initialization
@SuppressWarnings("initialization.field.uninitialized")
// All remaining positional arguments are parsed as module names.
@Parameters(index = "1", arity = "1..*", description = "Refactoring module(s) to use. Valid values: ${COMPLETION-CANDIDATES}", completionCandidates = ValidRefactoringModules.class)
private List<String> refactoringModules;

@Option(names = "--search", description = "Only search for valid refactorings without actually refactoring")
private static boolean searchOnly = false;

// Parses command-line arguments and executes run()
public static void main(String[] args) {
int exitCode = new CommandLine(new VGRTool()).execute(args);
System.exit(exitCode);
}

/**
* Main method for the program; Runs refactorings to all Java files in a given
* directory
*
* @param args
* Path of directory to execute program on (args[0]) and refactoring
* modules to use (args[1...])
*/
public static void main(String[] args) {
if (args.length < 2) {

LOGGER.info("Usage: java VGRTool <sourceDirPath> <refactoringModules>");
LOGGER.info("Available Modules:");
LOGGER.info(" - " + AddNullCheckBeforeDereferenceRefactoring.NAME);
LOGGER.info(" - " + NestedNullRefactoring.NAME);
System.exit(1);
}

String targetDir = args[0];
String refactoringModule = args[1];

public void run() {
LOGGER.debug("Processing directory: {}", targetDir);
LOGGER.debug("Selected Refactoring Module: {}", refactoringModule);
LOGGER.debug("Selected Refactoring Module(s): {}", refactoringModules);

try {
// Step 1: Collect all Java files in the target directory
List<File> javaFiles = getJavaFiles(targetDir);

// Step 2: Process each Java file using the selected refactoring module
// Step 2: Process each Java file using the selected refactoring module(s)
for (File file : javaFiles) {
LOGGER.debug("Processing file: {}", file.getPath());
processFile(file, refactoringModule);
List<Refactoring> selectedRefactorings = processRefactorings(refactoringModules);
processFile(file, selectedRefactorings);
}

LOGGER.info("Refactoring completed successfully!");
Expand All @@ -67,6 +87,35 @@ public static void main(String[] args) {
}
}

/**
* Parses and converts {@value refactoringModuleNames} into a list of
* refactorings without duplicates.
*
* @param refactoringModuleNames
* A List<String> of refactoringModule names to parse
**/
private static List<Refactoring> processRefactorings(List<String> refactoringModuleNames) {
List<Refactoring> refactorings = new ArrayList<>();
for (String name : refactoringModuleNames) {
switch (name) {
case AddNullCheckBeforeDereferenceRefactoring.NAME ->
refactorings.add(new AddNullCheckBeforeDereferenceRefactoring());
case BooleanFlagRefactoring.NAME -> refactorings.add(new BooleanFlagRefactoring());
case SentinelRefactoring.NAME -> refactorings.add(new SentinelRefactoring());
case NestedNullRefactoring.NAME -> refactorings.add(new NestedNullRefactoring());
case "All" -> refactorings.addAll(Arrays.asList(new AddNullCheckBeforeDereferenceRefactoring(),
new BooleanFlagRefactoring(), new SentinelRefactoring(), new NestedNullRefactoring()));
// Should already be caught by Picocli
default -> throw new IllegalArgumentException("Unknown refactoring module: " + name);
}
}

if (refactorings.isEmpty()) {
throw new IllegalArgumentException("No valid refactorings specified");
}
return refactorings;
}

/**
* Returns a list of all java files in the given directory path
*
Expand All @@ -86,10 +135,10 @@ public static void main(String[] args) {
*
* @param file
* The file to refactor
* @param refactoringModule
* @param refactoringModules
* the refactoring to apply to the file
*/
private static void processFile(File file, String refactoringModule) {
private static void processFile(File file, List<Refactoring> refactoringModules) {
try {
// Step 3: Read the file content
String content = Files.readString(file.toPath());
Expand All @@ -114,18 +163,22 @@ private static void processFile(File file, String refactoringModule) {

CompilationUnit cu = (CompilationUnit) parser.createAST(null);

// Step 5: Initialize RefactoringEngine with the selected module
List<String> selectedModules = Collections.singletonList(refactoringModule);
RefactoringEngine refactoringEngine = new RefactoringEngine(selectedModules);
// Step 5: Initialize RefactoringEngine with the selected modules
RefactoringEngine refactoringEngine = new RefactoringEngine(refactoringModules);

// Step 6: Apply refactorings using RefactoringEngine
// Step 6: Search or apply refactorings using RefactoringEngine
if (searchOnly) {
String filePath = file.getPath();
String fileName = file.getName();
refactoringEngine.searchRefactorings(cu, content, filePath, fileName);
return;
}
String refactoredSourceCode = refactoringEngine.applyRefactorings(cu, content);

// Step 7: Write the refactored code back to the file
Files.writeString(file.toPath(), refactoredSourceCode);

LOGGER.info("Refactored file saved: {}", file.getPath());

} catch (IOException e) {
LOGGER.error("Error processing file: {}", file.getPath(), e);
}
Expand Down
28 changes: 28 additions & 0 deletions src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,38 @@
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%-5level] [%logger{36}]: %msg%n"/>
</Console>

<!-- Creates a new file every time the program is run, rolling over the old file -->
<!-- The new file will be named according to fileName. -->
<!-- The rolled over file will be named according to filePattern: appended with the date and time of the rollover -->
<RollingFile name="SearchResults"
fileName="searchResults/searchResults.csv"
filePattern="searchResults/searchResults-%d{yyyy-MM-dd-HH-mm-ss}.csv"
createOnDemand="true"
append="true">

<!-- Only log passed message -->
<PatternLayout pattern="%msg%n">
<!-- Header is written once when the file is created -->
<Header>Refactoring Module,File Path,File Name,Line Number,Node Type,Code Snippet%n</Header>
</PatternLayout>

<Policies>
<!-- Rollover file every time the program is run -->
<OnStartupTriggeringPolicy />
</Policies>
</RollingFile>
</Appenders>

<Loggers>
<!-- All level "debug" and above logs sent to default logger are printed to the Console -->
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>

<!-- Logger for search results -->
<Logger name="SearchLogger" level="info" additivity="false">
<AppenderRef ref="SearchResults"/>
</Logger>
</Loggers>
</Configuration>
2 changes: 1 addition & 1 deletion src/test/java/BooleanFlagTesting.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
public class BooleanFlagTesting {

public void test(String input, String expectedOutput) {
TestingEngine.testSingleRefactoring(input, expectedOutput, "BooleanFlagRefactoring");
TestingEngine.testSingleRefactoring(input, expectedOutput, new BooleanFlagRefactoring());
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/DereferenceTesting.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
public class DereferenceTesting {

public void test(String input, String expectedOutput) {
TestingEngine.testSingleRefactoring(input, expectedOutput, AddNullCheckBeforeDereferenceRefactoring.NAME);
TestingEngine.testSingleRefactoring(input, expectedOutput, new AddNullCheckBeforeDereferenceRefactoring());
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/NestedNullTesting.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public class NestedNullTesting {

public void test(String input, String expectedOutput) {
TestingEngine.testSingleRefactoring(input, expectedOutput, "NestedNullRefactoring");
TestingEngine.testSingleRefactoring(input, expectedOutput, new NestedNullRefactoring());
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/SentinelTesting.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
public class SentinelTesting {

public void test(String input, String expectedOutput) {
TestingEngine.testSingleRefactoring(input, expectedOutput, "SentinelRefactoring");
TestingEngine.testSingleRefactoring(input, expectedOutput, new SentinelRefactoring());
}

@Test
Expand Down
6 changes: 3 additions & 3 deletions src/test/java/TestingEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public class TestingEngine {
* RefactoringEngine to use to run tests
*/
private static RefactoringEngine fullEngine = new RefactoringEngine(
Lists.newArrayList(AddNullCheckBeforeDereferenceRefactoring.NAME, BooleanFlagRefactoring.NAME,
NestedNullRefactoring.NAME, SentinelRefactoring.NAME));
Lists.newArrayList(new AddNullCheckBeforeDereferenceRefactoring(), new BooleanFlagRefactoring(),
new NestedNullRefactoring(), new SentinelRefactoring()));

// TODO: WRITE VARIANTS FOR SUPPORTED JAVA VERSIONS
private static ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); // Use appropriate
Expand All @@ -31,7 +31,7 @@ public static void testAllRefactorings(String input, String expectedOutput) {
runTest(input, expectedOutput, fullEngine);
}

public static void testSingleRefactoring(String input, String expectedOutput, String refactoring) {
public static void testSingleRefactoring(String input, String expectedOutput, Refactoring refactoring) {
runTest(input, expectedOutput, new RefactoringEngine(Collections.singletonList(refactoring)));
}

Expand Down
Loading