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

Addressing tutor comments #189

Merged
merged 6 commits into from
Nov 2, 2016
Merged
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
15 changes: 15 additions & 0 deletions src/main/java/seedu/agendum/logic/parser/DateTimeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@

//@@author A0003878Y

/**
* Utilities for DateTime parsing
*/
public class DateTimeUtils {

/**
* Parses input string into LocalDateTime objects using Natural Language Parsing
* @param input natural language date time string
* @return Optional is null if input coult not be parsed
*/
public static Optional<LocalDateTime> parseNaturalLanguageDateTimeString(String input) {
if(input == null || input.isEmpty()) {
return Optional.empty();
Expand All @@ -36,6 +44,13 @@ public static Optional<LocalDateTime> parseNaturalLanguageDateTimeString(String
return Optional.ofNullable(localDateTime);
}

/**
* Takes two LocalDateTime and balances by ensuring that the latter DateTime is gaurenteed to be later
* than the former DateTime
* @param startDateTime
* @param endDateTime
* @return endDateTime that is now balanced
*/
public static LocalDateTime balanceStartAndEndDateTime(LocalDateTime startDateTime, LocalDateTime endDateTime) {
LocalDateTime newEndDateTime = endDateTime;
while (startDateTime.compareTo(newEndDateTime) >= 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@
import java.util.logging.Logger;

//@@author A0003878Y

/**
* A static class for calculating levenshtein distance between two strings
*/
public class EditDistanceCalculator {

private static final Logger logger = LogsCenter.getLogger(EditDistanceCalculator.class);
private static final int EDIT_DISTANCE_THRESHOLD = 3;

/**
* Attempts to find the 'closest' command for an input String
* @param input user inputted command
* @return Optional string that's the closest command to input. Null if not found.
*/
public static Optional<String> closestCommandMatch(String input) {
Reflections reflections = new Reflections("seedu.agendum");
Set<Class<? extends Command>> classes = reflections.getSubTypesOf(Command.class);

final String[] bestCommand = {""};
final int[] bestCommandDistance = {Integer.MAX_VALUE};

Expand All @@ -32,7 +38,6 @@ public static Optional<String> closestCommandMatch(String input) {
bestCommandDistance[0] = commandWordDistance;
}
};

executeOnAllCommands(consumer);

if (bestCommandDistance[0] < EDIT_DISTANCE_THRESHOLD) {
Expand All @@ -42,15 +47,20 @@ public static Optional<String> closestCommandMatch(String input) {
}
}

public static Optional<String> commandCompletion(String input) {
/**
* Attempts to 'complete' the input String into an actual command
* @param input user inputted command
* @return Optional string that's command that best completes the input. If input matches more than
* one command, null ire returned. Null is also returned if a command is not found.
*/
public static Optional<String> findCommandCompletion(String input) {
ArrayList<String> matchedCommands = new ArrayList<>();

Consumer<String> consumer = (commandWord) -> {
if (commandWord.startsWith(input)) {
matchedCommands.add(commandWord);
}
};

executeOnAllCommands(consumer);

if (matchedCommands.size() == 1) {
Expand All @@ -60,14 +70,19 @@ public static Optional<String> commandCompletion(String input) {
}
}

private static void executeOnAllCommands(Consumer f) {
/**
* A higher order method that takes in an operation to perform on all Commands using
* Java reflection and functional programming paradigm.
* @param f A closure that takes a String as input that executes on all Commands.
*/
private static void executeOnAllCommands(Consumer<String> f) {
new Reflections("seedu.agendum").getSubTypesOf(Command.class)
.stream()
.map(s -> {
try {
return s.getMethod("getName").invoke(null).toString();
} catch (NullPointerException e) {
return "";
return ""; // Suppress this exception are we expect some Commands to not conform to getName()
} catch (Exception e) {
logger.severe("Java reflection for Command class failed");
throw new RuntimeException();
Expand All @@ -78,7 +93,13 @@ private static void executeOnAllCommands(Consumer f) {
}


// Code from https://rosettacode.org/wiki/Levenshtein_distance#Java
/**
* Calculates levenshtein distnace between two strings.
* Code from https://rosettacode.org/wiki/Levenshtein_distance#Java
* @param a
* @param b
* @return
*/
private static int distance(String a, String b) {
a = a.toLowerCase();
b = b.toLowerCase();
Expand Down
67 changes: 39 additions & 28 deletions src/main/java/seedu/agendum/logic/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -150,30 +151,28 @@ private Command prepareAdd(String args) {
try {
matcher.reset();
matcher.find();
String taskTitle = matcher.group(0);
StringBuilder titleBuilder = new StringBuilder(matcher.group(0));
HashMap<String, Optional<LocalDateTime>> dateTimeMap = new HashMap<>();

while (matcher.find()) {
for (String token : TIME_TOKENS) {
String s = matcher.group(0).toLowerCase();
if (s.startsWith(token)) {
String time = s.substring(token.length(), s.length());
if (DateTimeUtils.containsTime(time)) {
dateTimeMap.put(token, DateTimeUtils.parseNaturalLanguageDateTimeString(time));
} else {
taskTitle = taskTitle + s;
}
}
BiConsumer<String, String> consumer = (matchedGroup, token) -> {
String time = matchedGroup.substring(token.length(), matchedGroup.length());
if (DateTimeUtils.containsTime(time)) {
dateTimeMap.put(token, DateTimeUtils.parseNaturalLanguageDateTimeString(time));
} else {
titleBuilder.append(matchedGroup);
}
}
};
scheduleMatcherOnConsumer(matcher, consumer);

String title = titleBuilder.toString();

if (dateTimeMap.containsKey(ARGS_BY)) {
return new AddCommand(taskTitle, dateTimeMap.get(ARGS_BY));
return new AddCommand(title, dateTimeMap.get(ARGS_BY));
} else if (dateTimeMap.containsKey(ARGS_FROM) && dateTimeMap.containsKey(ARGS_TO)) {
return new AddCommand(taskTitle, dateTimeMap.get(ARGS_FROM), dateTimeMap.get(ARGS_TO));
return new AddCommand(title, dateTimeMap.get(ARGS_FROM), dateTimeMap.get(ARGS_TO));
} else if (!dateTimeMap.containsKey(ARGS_FROM) && !dateTimeMap.containsKey(ARGS_TO)
&& !dateTimeMap.containsKey(ARGS_BY)) {
return new AddCommand(taskTitle);
return new AddCommand(title);
} else {
return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
AddCommand.MESSAGE_USAGE));
Expand All @@ -199,6 +198,7 @@ private Command prepareSchedule(String args) {

matcher.reset();
matcher.find();
HashMap<String, Optional<LocalDateTime>> dateTimeMap = new HashMap<>();
Optional<Integer> taskIndex = parseIndex(matcher.group(0));
int index = 0;
if (taskIndex.isPresent()) {
Expand All @@ -207,20 +207,14 @@ private Command prepareSchedule(String args) {
return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
ScheduleCommand.MESSAGE_USAGE));
}

HashMap<String, Optional<LocalDateTime>> dateTimeMap = new HashMap<>();

while (matcher.find()) {
for (String token : TIME_TOKENS) {
String s = matcher.group(0).toLowerCase();
if (s.startsWith(token)) {
String time = s.substring(token.length(), s.length());
if (DateTimeUtils.containsTime(time)) {
dateTimeMap.put(token, DateTimeUtils.parseNaturalLanguageDateTimeString(time));
}
}
BiConsumer<String, String> consumer = (matchedGroup, token) -> {
String time = matchedGroup.substring(token.length(), matchedGroup.length());
if (DateTimeUtils.containsTime(time)) {
dateTimeMap.put(token, DateTimeUtils.parseNaturalLanguageDateTimeString(time));
}
}
};
scheduleMatcherOnConsumer(matcher, consumer);

if (dateTimeMap.containsKey(ARGS_BY)) {
return new ScheduleCommand(index, Optional.empty(), dateTimeMap.get(ARGS_BY));
Expand All @@ -234,6 +228,23 @@ private Command prepareSchedule(String args) {
String.format(MESSAGE_INVALID_COMMAND_FORMAT, ScheduleCommand.MESSAGE_USAGE));
}
}

/**
* Parses arguments in the context of the schedule task command.
*
* @param matcher matcher for current command context
* @param consumer <String, String> closure to execute on
*/
private void scheduleMatcherOnConsumer(Matcher matcher, BiConsumer<String, String> consumer) {
while (matcher.find()) {
for (String token : TIME_TOKENS) {
String matchedGroup = matcher.group(0).toLowerCase();
if (matchedGroup.startsWith(token)) {
consumer.accept(matchedGroup, token);
}
}
}
}


//@@author A0133367E
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/seedu/agendum/ui/CommandBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private void registerTabKeyEventFilter() {
commandTextField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
KeyCode keyCode = event.getCode();
if (keyCode.equals(KeyCode.TAB)) {
Optional<String> parsedString = EditDistanceCalculator.commandCompletion(commandTextField.getText());
Optional<String> parsedString = EditDistanceCalculator.findCommandCompletion(commandTextField.getText());
if(parsedString.isPresent()) {
commandTextField.setText(parsedString.get());
}
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/seedu/agendum/ui/HelpWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,12 @@ public void show(double height) {
}

//@@author A0003878Y

/**
* Uses Java reflection followed by Java stream.map() to retrieve all commands for listing on the Help
* window dynamically
*/
private void loadHelpList() {

new Reflections("seedu.agendum").getSubTypesOf(Command.class)
.stream()
.map(s -> {
Expand All @@ -120,7 +124,7 @@ private void loadHelpList() {
map.put(CommandColumns.DESCRIPTION, s.getMethod("getDescription").invoke(null).toString());
return map;
} catch (NullPointerException e) {
return null;
return null; // Suppress this exception are we expect some Commands to not conform to these methods
} catch (Exception e) {
logger.severe("Java reflection for Command class failed");
throw new RuntimeException();
Expand Down
20 changes: 10 additions & 10 deletions src/test/java/seedu/agendum/logic/EditDistanceCalculatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ public void closestCommandMatchTest() throws Exception {

@Test
public void commandCompletion() throws Exception {
assertEquals(EditDistanceCalculator.commandCompletion("ad").get(), "add");
assertEquals(EditDistanceCalculator.commandCompletion("ma").get(), "mark");
assertEquals(EditDistanceCalculator.commandCompletion("un"), Optional.empty()); // ambiguous returns nothing. Can be undo or unmark
assertEquals(EditDistanceCalculator.commandCompletion("unm").get(), "unmark");
assertEquals(EditDistanceCalculator.commandCompletion("und").get(), "undo");
assertEquals(EditDistanceCalculator.commandCompletion("st").get(), "store");
assertEquals(EditDistanceCalculator.commandCompletion("de").get(), "delete");
assertEquals(EditDistanceCalculator.commandCompletion("he").get(), "help");
assertEquals(EditDistanceCalculator.commandCompletion("sc").get(), "schedule");
assertEquals(EditDistanceCalculator.commandCompletion("r").get(), "rename");
assertEquals(EditDistanceCalculator.findCommandCompletion("ad").get(), "add");
assertEquals(EditDistanceCalculator.findCommandCompletion("ma").get(), "mark");
assertEquals(EditDistanceCalculator.findCommandCompletion("un"), Optional.empty()); // ambiguous returns nothing. Can be undo or unmark
assertEquals(EditDistanceCalculator.findCommandCompletion("unm").get(), "unmark");
assertEquals(EditDistanceCalculator.findCommandCompletion("und").get(), "undo");
assertEquals(EditDistanceCalculator.findCommandCompletion("st").get(), "store");
assertEquals(EditDistanceCalculator.findCommandCompletion("de").get(), "delete");
assertEquals(EditDistanceCalculator.findCommandCompletion("he").get(), "help");
assertEquals(EditDistanceCalculator.findCommandCompletion("sc").get(), "schedule");
assertEquals(EditDistanceCalculator.findCommandCompletion("r").get(), "rename");
}

}