Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUI initial revision, among other things #59

Merged
merged 11 commits into from
Apr 27, 2024
84 changes: 14 additions & 70 deletions src/main/java/cli/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,12 @@
import datasource.Configuration;
import datasource.FilesLoader;
import domain.Check;
import domain.CheckUtil;
import domain.Message;
import domain.MessageLevel;
import domain.javadata.ClassData;
import domain.javadata.ClassDataCollection;
import domain.javadata.ClassReaderUtil;

class App {
private static final String CLASS_FILE_EXT = "class";
private static final String ENABLE_KEY_PREFIX = "enable_";
private static final String SKIP_UNMARKED_CHECKS_KEY = "skipUnmarkedChecks";

private static final Map<MessageLevel, TerminalTextColor> MESSAGE_LEVEL_COLORS = Map.of(
MessageLevel.ERROR, TerminalTextColor.RED,
MessageLevel.WARNING, TerminalTextColor.YELLOW,
Expand All @@ -47,7 +42,7 @@ boolean run(Check[] checks) {
Set<byte[]> classFiles;
Configuration config;
try {
classFiles = this.filesLoader.loadFiles(CLASS_FILE_EXT);
classFiles = this.filesLoader.loadFiles(CheckUtil.CLASS_FILE_EXT);
} catch (IllegalStateException ex) { // dir not found
this.errStream.println(ex.getMessage());
return false;
Expand All @@ -66,65 +61,23 @@ boolean run(Check[] checks) {
}
}

ClassDataCollection classes = readInClasses(classFiles);
Map<MessageLevel, Integer> msgTotals = this.runAllChecksAndPrintResults(checks, classes, config);
return msgTotals.get(MessageLevel.ERROR) == 0;
}

private static ClassDataCollection readInClasses(Set<byte[]> classFiles) {
ClassDataCollection classes = new ClassDataCollection();
for (byte[] classFile : classFiles) {
ClassData classData = ClassReaderUtil.read(classFile);
classes.add(classData);
}
return classes;
}

private Map<MessageLevel, Integer> runAllChecksAndPrintResults(
Check[] checks, ClassDataCollection classes, Configuration config
) {
boolean skipUnmarked = Boolean.TRUE.equals(this.configBoolOrNull(config, SKIP_UNMARKED_CHECKS_KEY));
Map<MessageLevel, Integer> msgTotals = initMsgTotals();
int numChecksRun = 0;
for (Check check : checks) {
Boolean configVal = this.configBoolOrNull(config, ENABLE_KEY_PREFIX + check.name);
if (skipUnmarked ? Boolean.TRUE.equals(configVal) : check.isEnabled(configVal)) {
this.runCheckAndPrintResults(check, classes, config, msgTotals);
numChecksRun++;
}
}
ClassDataCollection classes = CheckUtil.readInClasses(classFiles);
Map<MessageLevel, Integer> msgTotals = new HashMap<MessageLevel, Integer>();
int numChecksRun = CheckUtil.runAllChecks(checks, classes, config, msgTotals, this::printCheckResults);
this.outStream.println(MessageFormat.format("Checks run: {0}", numChecksRun));
this.printTotals(msgTotals);
return msgTotals;
return msgTotals.get(MessageLevel.ERROR) == 0;
}

private Boolean configBoolOrNull(Configuration config, String key) {
try {
return config.getBoolean(key);
} catch (IllegalArgumentException ex) {
return null;
} catch (ClassCastException ex) {
this.errStream.println(MessageFormat.format(
"Error in config: property \"{0}\", if present, should be boolean",
key
));
return null;
private void printCheckResults(String checkName, Set<Message> generatedMsgs) {
this.outStream.println(MessageFormat.format("Check {0}:", checkName));
for (Message msg : generatedMsgs) {
this.outStream.println(MessageFormat.format("\t{0}", colorMessageTag(msg)));
}
}

private void runCheckAndPrintResults(
Check check, ClassDataCollection classes, Configuration config, Map<MessageLevel, Integer> msgTotals
) {
Set<Message> generatedMsgs = check.run(classes, config);
this.outStream.println(MessageFormat.format("Check {0}:", check.name));
for (Message msg : generatedMsgs) {
msgTotals.put(msg.level, msgTotals.get(msg.level) + 1);
this.outStream.println(MessageFormat.format("\t{0}", colorMessageTag(msg)));
}
if (generatedMsgs.isEmpty()) {
this.outStream.println(MessageFormat.format("\t{0}",TerminalTextColor.BLACK.applyTo("(no messages)")));
}
this.outStream.println();
if (generatedMsgs.isEmpty()) {
this.outStream.println(MessageFormat.format("\t{0}",TerminalTextColor.BLACK.applyTo("(no messages)")));
}
this.outStream.println();
}

private static String colorMessageTag(Message msg) {
Expand All @@ -141,7 +94,6 @@ private void printTotals(Map<MessageLevel, Integer> msgTotals) {
}
if (totalMsgs == 0) {
this.outStream.println(TerminalTextColor.GREEN.applyTo("No messages generated."));
return;
} else {
List<String> totalsTerms = new ArrayList<>();
generateTotalsTerm(totalsTerms, MessageLevel.ERROR, msgTotals.get(MessageLevel.ERROR));
Expand All @@ -158,12 +110,4 @@ private static void generateTotalsTerm(List<String> totalsTerms, MessageLevel le
));
}
}

private static Map<MessageLevel, Integer> initMsgTotals() {
Map<MessageLevel, Integer> msgTotals = new HashMap<>();
for (MessageLevel level : MessageLevel.values()) {
msgTotals.put(level, 0);
}
return msgTotals;
}
}
7 changes: 5 additions & 2 deletions src/main/java/cli/Main.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package cli;

import java.io.IOException;
import java.text.MessageFormat;

import datasource.DirLoader;
import datasource.JsonFileConfigLoader;
import domain.CheckRoster;
import general.ProductInfo;

public final class Main {
public static void main(String[] args) throws IOException {
if (args.length == 0) {
printUsage();
printInfo();
System.exit(1);
} else {
String targetDirPath = args[0];
Expand All @@ -24,7 +26,8 @@ public static void main(String[] args) throws IOException {
}
}

private static void printUsage() {
private static void printInfo() {
System.out.println(MessageFormat.format("{0} version {1}", ProductInfo.NAME, ProductInfo.VERSION));
System.out.println("usage: <command to run LinterProject> <classdir> [<config>]");
}
}
8 changes: 7 additions & 1 deletion src/main/java/datasource/JsonFileConfigLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.nio.file.Path;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

/**
Expand All @@ -21,7 +22,12 @@ public JsonFileConfigLoader(String path) {
public Configuration loadConfig() throws IOException {
List<String> lines = Files.readAllLines(Path.of(this.path));
String json = String.join("\n", lines);
JSONObject jsonObject = new JSONObject(json);
JSONObject jsonObject;
try {
jsonObject = new JSONObject(json);
} catch (JSONException ex) {
throw new IOException(ex.getMessage());
}
return new Configuration(jsonObject.toMap());
}
}
94 changes: 94 additions & 0 deletions src/main/java/domain/CheckUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package domain;

import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;

import datasource.Configuration;
import domain.javadata.ClassData;
import domain.javadata.ClassDataCollection;
import domain.javadata.ClassReaderUtil;

/**
* Methods used for running checks, called by presentation layers (CLI or GUI).
*/
public final class CheckUtil {
/**
* File name extension for Java class files.
*/
public static final String CLASS_FILE_EXT = "class";

private static final String ENABLE_KEY_PREFIX = "enable_";
private static final String SKIP_UNMARKED_CHECKS_KEY = "skipUnmarkedChecks";

/**
* Parses Java bytecode into ClassData objects.
* @param classFiles set of Java class files' byte arrays
* @return ClassDataCollection of the generated ClassData objects
*/
public static ClassDataCollection readInClasses(Set<byte[]> classFiles) {
ClassDataCollection classes = new ClassDataCollection();
for (byte[] classFile : classFiles) {
ClassData classData = ClassReaderUtil.read(classFile);
classes.add(classData);
}
return classes;
}

/**
* Runs checks on the given classes in accordance with the given configuration.
* @param checks all checks (runAllChecks will filter using config)
* @param classes ClassDataCollection to run checks on
* @param config user's configuration
* @param msgTotals initially empty mutable map that will be populated with message totals
* @param resultsHandler function that takes a check name and the set of messages generated by that check (for output)
* @return number of checks run
*/
public static int runAllChecks(
Check[] checks,
ClassDataCollection classes,
Configuration config,
Map<MessageLevel, Integer> msgTotals,
BiConsumer<String, Set<Message>> resultsHandler
) {
initMsgTotals(msgTotals);
boolean skipUnmarked = Boolean.TRUE.equals(configBoolOrNull(config, SKIP_UNMARKED_CHECKS_KEY));
int numChecksRun = 0;
for (Check check : checks) {
Boolean configVal = configBoolOrNull(config, ENABLE_KEY_PREFIX + check.name);
if (skipUnmarked ? Boolean.TRUE.equals(configVal) : check.isEnabled(configVal)) {
runCheck(check, classes, config, msgTotals, resultsHandler);
numChecksRun++;
}
}
return numChecksRun;
}

private static void runCheck(
Check check,
ClassDataCollection classes,
Configuration config,
Map<MessageLevel, Integer> msgTotals,
BiConsumer<String, Set<Message>> resultsHandler
) {
Set<Message> generatedMsgs = check.run(classes, config);
for (Message msg : generatedMsgs) {
msgTotals.put(msg.level, msgTotals.get(msg.level) + 1);
}
resultsHandler.accept(check.name, generatedMsgs);
}

private static Boolean configBoolOrNull(Configuration config, String key) {
try {
return config.getBoolean(key);
} catch (IllegalArgumentException | ClassCastException ex) {
return null;
}
}

private static void initMsgTotals(Map<MessageLevel, Integer> msgTotals) {
for (MessageLevel level : MessageLevel.values()) {
msgTotals.put(level, 0);
}
}
}
8 changes: 6 additions & 2 deletions src/main/java/domain/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ public Set<String> getClassFullNames() {

@Override
public String toString() {
String msg = MessageFormat.format(
return MessageFormat.format(
"[{0}] {1}",
this.level.abbreviation.toUpperCase(), this.text
this.level.abbreviation.toUpperCase(), this.toStringWithoutLevel()
);
}

public String toStringWithoutLevel() {
String msg = this.text;
String classFullNamesStr = String.join(", ", this.classFullNames.toArray(new String[this.classFullNames.size()]));
if (!classFullNamesStr.isEmpty()) {
msg = MessageFormat.format(
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/general/ProductInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package general;

public final class ProductInfo {
public static final String NAME = "Linter";
public static final String VERSION = "UNRELEASED";
}
Loading
Loading