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

Adding analyzer for need for speed concept exercise #134

Merged
merged 9 commits into from
Feb 16, 2024
2 changes: 2 additions & 0 deletions src/main/java/analyzer/AnalyzerRoot.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import analyzer.exercises.hamming.HammingAnalyzer;
import analyzer.exercises.lasagna.LasagnaAnalyzer;
import analyzer.exercises.leap.LeapAnalyzer;
import analyzer.exercises.needforspeed.NeedForSpeedAnalyzer;
import analyzer.exercises.twofer.TwoferAnalyzer;

import java.util.ArrayList;
Expand Down Expand Up @@ -50,6 +51,7 @@ private static List<Analyzer> createAnalyzers(String slug) {
case "hamming" -> analyzers.add(new HammingAnalyzer());
case "lasagna" -> analyzers.add(new LasagnaAnalyzer());
case "leap" -> analyzers.add(new LeapAnalyzer());
case "need-for-speed" -> analyzers.add(new NeedForSpeedAnalyzer());
manumafe98 marked this conversation as resolved.
Show resolved Hide resolved
case "two-fer" -> analyzers.add(new TwoferAnalyzer());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.exercises.needforspeed;

import analyzer.Comment;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/need-for-speed/avoid_conditional_logic.md">Markdown Template</a>
*/
class AvoidConditionalLogic extends Comment {

@Override
public String getKey() {
return "java.need-for-speed.avoid_conditional_logic";
}

@Override
public Type getType() {
return Type.ACTIONABLE;
}
}
19 changes: 19 additions & 0 deletions src/main/java/analyzer/exercises/needforspeed/AvoidLoops.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package analyzer.exercises.needforspeed;

import analyzer.Comment;

/**
* @see <a href="https://github.com/exercism/website-copy/blob/main/analyzer-comments/java/need-for-speed/avoid_loops.md">Markdown Template</a>
*/
class AvoidLoops extends Comment{

@Override
public String getKey() {
return "java.need-for-speed.avoid_loops";
}

@Override
public Type getType() {
return Type.ACTIONABLE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package analyzer.exercises.needforspeed;

import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.ConditionalExpr;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.ast.stmt.IfStmt;
import com.github.javaparser.ast.stmt.Statement;

import analyzer.Analyzer;
import analyzer.OutputCollector;
import analyzer.Solution;
import analyzer.comments.ExemplarSolution;

/**
* The {@link NeedForSpeedAnalyzer} is the analyzer implementation for the {@code need-for-speed} practice exercise.
* It has two subclasses NeedForSpeedClassAnalyzer and RaceTrackClassAnalyzer that extend the {@link VoidVisitorAdapter} and use the visitor pattern to traverse each compilation unit.
*
* @see <a href="https://github.com/exercism/java/tree/main/exercises/concept/need-for-speed">The need-for-speed exercise on the Java track</a>
*/
public class NeedForSpeedAnalyzer implements Analyzer {
private static final String EXERCISE_NAME = "Need for Speed";

@Override
public void analyze(Solution solution, OutputCollector output) {
var needForSpeedClassAnalyzer = new NeedForSpeedClassAnalyzer();
var raceTrackClassAnalyzer = new RaceTrackClassAnalyzer();

for (var compilationUnit : solution.getCompilationUnits()) {
compilationUnit.getClassByName("NeedForSpeed").ifPresent(c -> c.accept(needForSpeedClassAnalyzer, output));
compilationUnit.getClassByName("RaceTrack").ifPresent(c -> c.accept(raceTrackClassAnalyzer, output));
}

if (output.getComments().isEmpty()) {
output.addComment(new ExemplarSolution(EXERCISE_NAME));
}
}

class NeedForSpeedClassAnalyzer extends VoidVisitorAdapter<OutputCollector> {

@Override
public void visit(MethodDeclaration node, OutputCollector output) {
if (node.getNameAsString().equals("batteryDrained") && hasConditional(node)) {
output.addComment(new AvoidConditionalLogic());
}

super.visit(node, output);
}

private static boolean hasConditional(MethodDeclaration node) {
return node.getBody()
.map(body -> body.getStatements().stream()
.anyMatch(NeedForSpeedClassAnalyzer::isConditionalExpresion))
.orElse(false);
}

private static boolean isConditionalExpresion(Statement statement) {
return !statement.findAll(IfStmt.class).isEmpty() || !statement.findAll(ConditionalExpr.class).isEmpty();
}
}

class RaceTrackClassAnalyzer extends VoidVisitorAdapter<OutputCollector> {

@Override
public void visit(MethodDeclaration node, OutputCollector output) {
if (node.getNameAsString().equals("tryFinishTrack") && hasLoop(node)) {
output.addComment(new AvoidLoops());
}

super.visit(node, output);
}

private static boolean hasLoop(MethodDeclaration node) {
return node.getBody()
.map(body -> body.getStatements().stream().anyMatch(RaceTrackClassAnalyzer::isLoopStatement))
.orElse(false);
}

private static boolean isLoopStatement(Statement statement) {
return statement.isForStmt() || statement.isForEachStmt() || statement.isWhileStmt() || statement.isDoStmt();
}
}
}
16 changes: 16 additions & 0 deletions src/test/java/analyzer/AnalyzerIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,20 @@ public void annalynsinfiltration(String scenario) throws IOException {

Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario));
}

@ParameterizedTest
@ValueSource(strings = {
"ExemplarSolution",
"UsingForLoop",
"UsingIfStatement",
"UsingTernary",
"UsingWhileLoop",
})
void needforspeed(String scenario) throws IOException {
var path = Path.of("need-for-speed", scenario + ".java");
var solution = new SolutionFromFiles("need-for-speed", SCENARIOS.resolve(path));
var output = AnalyzerRoot.analyze(solution);

Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"comments": [
{
"comment": "java.general.exemplar",
"params": {
"exerciseName": "Need for Speed"
},
"type": "celebratory"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"comments": [
{
"comment": "java.need-for-speed.avoid_loops",
"params": {},
"type": "actionable"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"comments": [
{
"comment": "java.need-for-speed.avoid_conditional_logic",
"params": {},
"type": "actionable"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"comments": [
{
"comment": "java.need-for-speed.avoid_conditional_logic",
"params": {},
"type": "actionable"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"comments": [
{
"comment": "java.need-for-speed.avoid_loops",
"params": {},
"type": "actionable"
},
{
"comment": "java.general.feedback_request",
"params": {},
"type": "informative"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package scenarios.neeedforspeed;

public class NeedForSpeed {
private int speed;
private int batteryDrain;
private int distance = 0;
private int battery = 100;

public NeedForSpeed(int speed, int batteryDrain) {
this.speed = speed;
this.batteryDrain = batteryDrain;
}

public static NeedForSpeed nitro() {
return new NeedForSpeed(50, 4);
}

public boolean batteryDrained() {
return battery < batteryDrain;
}

public int distanceDriven() {
return distance;
}

public int getSpeed() {
return speed;
}

public int getBatteryDrain() {
return batteryDrain;
}

public int getCurrentBattery() {
return battery;
}

public void drive() {
if (!batteryDrained()) {
battery -= batteryDrain;
distance += speed;
}
}
}

class RaceTrack {
private int distance;

RaceTrack(int distance) {
this.distance = distance;
}

public boolean tryFinishTrack(NeedForSpeed car) {
return ((double) distance / car.getSpeed()) <= (car.getCurrentBattery() / car.getBatteryDrain());
}
}
55 changes: 55 additions & 0 deletions src/test/resources/scenarios/need-for-speed/UsingForLoop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package scenarios.neeedforspeed;

public class NeedForSpeed {
private int speed;
private int batteryDrain;
private int distance = 0;
private int battery = 100;

public NeedForSpeed(int speed, int batteryDrain) {
this.speed = speed;
this.batteryDrain = batteryDrain;
}

public static NeedForSpeed nitro() {
return new NeedForSpeed(50, 4);
}

public boolean batteryDrained() {
return battery < batteryDrain;
}

public int distanceDriven() {
return distance;
}

public int getSpeed() {
return speed;
}

public void drive() {
if (!batteryDrained()) {
battery -= batteryDrain;
distance += speed;
}
}
}

class RaceTrack {
private int distance;

RaceTrack(int distance) {
this.distance = distance;
}

public boolean tryFinishTrack(NeedForSpeed car) {
for (int i = 0; i < this.distance / car.getSpeed(); i++) {
car.drive();
}

if (car.distanceDriven() >= distance) {
return true;
} else
return false;
}
}
Loading