Java tutorial
// Copyright 2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package codeu.chat.client.commandline; import java.util.Scanner; import java.util.regex.Pattern; import java.util.Set; import java.util.HashSet; import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; import java.util.HashMap; import java.lang.StringBuilder; import codeu.chat.client.ClientContext; import codeu.chat.client.Controller; import codeu.chat.client.View; import codeu.chat.common.ConversationSummary; import codeu.chat.util.Logger; import codeu.chat.common.User; import org.jsoup.Jsoup; import org.jsoup.helper.Validate; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.apache.commons.lang3.StringUtils; import java.io.IOException; // Chat - top-level client application. public final class Chat { private final static Logger.Log LOG = Logger.newLog(Chat.class); private static final String PROMPT = ">>"; private static final String BOT_NAME = "Bot"; private static final String BOT_CONVO = "Convo with Bot"; private static String response = ""; private static final String HOWRU_RESP = "Cool, that's good to hear!"; private static final String NO_MSGS = "C'mon, send something original!"; private static final Set<String> userPhraseSet = new HashSet<String>(); private static final List<String> userPhraseList = new ArrayList<String>(); public static final HashMap<String, ArrayList<String>> userPhraseMap = new HashMap<String, ArrayList<String>>(); public static final HashMap<String, String> responseMap = new HashMap<String, String>(); public static final HashMap<String, ArrayList<String>> scriptMap = new HashMap<String, ArrayList<String>>(); private static final Random generator = new Random(); // Variables used for adjusting responses private boolean SWITCH_NAME = true; private boolean SWITCH_YOUTOO = false; private static final String FORMAT_STR = "https://www.google.com/search?q=\"%s\"+movie+script"; private static final int MIN_LENGTH = 8; private static final int MAX_LINES = 4; private static String MATCHED_PHRASE = ""; private final static int PAGE_SIZE = 10; private boolean alive = true; private final ClientContext clientContext; // Constructor - sets up the Chat Application public Chat(Controller controller, View view) { clientContext = new ClientContext(controller, view); } // Print help message. private static void help() { System.out.println("Chat commands:"); System.out.println(" exit - exit the program."); System.out.println(" help - this help message."); System.out.println(" sign-in <username> - sign in as user <username>."); System.out.println(" sign-out - sign out current user."); System.out.println(" current - show current user, conversation, message."); System.out.println("User commands:"); System.out.println(" u-add <name> - add a new user."); System.out.println(" u-list-all - list all users known to system."); System.out.println("Conversation commands:"); System.out.println(" c-add <title> - add a new conversation."); System.out.println(" c-list-all - list all conversations known to system."); System.out.println(" c-search-title <title> - search for message by title."); System.out.println(" c-select <index> - select conversation from list."); System.out.println("Message commands:"); System.out.println(" m-add <body> - add a new message to the current conversation."); System.out.println(" m-search-string <string> - search for message containing string."); System.out.println(" m-search-author <author> - search for message by author."); System.out.println(" m-list-all - list all messages in the current conversation."); System.out.println(" m-next <index> - index of next message to view."); System.out.println(" m-show <count> - show next <count> messages."); System.out.println("Tag commands:"); System.out.println(" t-add <name> - add a new tag."); System.out.println(" t-list <name> - list all messages corresponding to tag"); System.out.println(" t-list-all - list all tags"); System.out.println(" t-list-user - list all tags for the current user"); } // Prompt for new command. private void promptForCommand() { System.out.print(PROMPT); } // Parse and execute a single command. private void doOneCommand(Scanner lineScanner) { final Scanner tokenScanner = new Scanner(lineScanner.nextLine()); if (!tokenScanner.hasNext()) { return; } final String token = tokenScanner.next(); if (token.equals("exit")) { alive = false; } else if (token.equals("help")) { help(); } else if (token.equals("sign-in")) { if (!tokenScanner.hasNext()) { System.out.println("ERROR: No user name supplied."); } else { signInUser(tokenScanner.nextLine().trim()); // Check whether the bot/convo already exist if (clientContext.user.lookup(BOT_NAME) == null) { addUser(BOT_NAME); clientContext.conversation.startConversation(BOT_CONVO, clientContext.user.getCurrent().id); } } } else if (token.equals("sign-out")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { signOutUser(); } } else if (token.equals("current")) { showCurrent(); } else if (token.equals("u-add")) { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Username not supplied."); } else { addUser(tokenScanner.nextLine().trim()); } } else if (token.equals("u-list-all")) { showAllUsers(); } else if (token.equals("c-add")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Conversation title not supplied."); } else { final String title = tokenScanner.nextLine().trim(); clientContext.conversation.startConversation(title, clientContext.user.getCurrent().id); } } } else if (token.equals("c-list-all")) { clientContext.conversation.showAllConversations(); } else if (token.equals("c-select")) { selectConversation(lineScanner); } else if (token.equals("c-search-title")) { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Conversation title not supplied."); } else { final String title = tokenScanner.nextLine().trim(); clientContext.conversation.searchTitle(title); } } else if (token.equals("m-add")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Message body not supplied."); } else { final String body = tokenScanner.nextLine().trim(); clientContext.message.addMessage(clientContext.user.getCurrent().id, clientContext.conversation.getCurrentId(), body); respondAsBot(body, true); } } } else if (token.equals("m-list-all")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else { clientContext.message.showAllMessages(); } } else if (token.equals("m-next")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else if (!tokenScanner.hasNextInt()) { System.out.println("Command requires an integer message index."); } else { clientContext.message.selectMessage(tokenScanner.nextInt()); } } else if (token.equals("m-show")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else { final int count = (tokenScanner.hasNextInt()) ? tokenScanner.nextInt() : 1; clientContext.message.showMessages(count); } } else if (token.equals("m-search-string")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else if (!tokenScanner.hasNext()) { System.out.println("ERROR: String not supplied."); } else { final String keyword = tokenScanner.nextLine().trim(); clientContext.message.searchString(keyword); } } else if (token.equals("m-search-author")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else if (!tokenScanner.hasNext()) { System.out.println("ERROR: author body not supplied."); } else { final String author = tokenScanner.nextLine().trim(); clientContext.message.searchAuthor(author); } } else if (token.equals("t-add")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { if (!tokenScanner.hasNext()) { System.out.println("ERROR: tag name not supplied."); } else { final String name = tokenScanner.nextLine().trim(); clientContext.tag.addTag(clientContext.user.getCurrent().id, name); } } } else if (token.equals("t-list")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { if (!tokenScanner.hasNext()) { System.out.println("ERROR: tag name not supplied."); } else { final String name = tokenScanner.nextLine().trim(); clientContext.tag.listTag(clientContext.user.getCurrent().id, name); } } } else if (token.equals("t-list-all")) { showAllTags(); } else if (token.equals("t-list-user")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { clientContext.tag.showUserTags(clientContext.user.getCurrent().id); } } else { System.out.format("Command not recognized: %s\n", token); System.out.format("Command line rejected: %s%s\n", token, (tokenScanner.hasNext()) ? tokenScanner.nextLine() : ""); System.out.println("Type \"help\" for help."); } tokenScanner.close(); } public String doOneTestCommand(Scanner lineScanner) { final Scanner tokenScanner = new Scanner(lineScanner.nextLine()); if (!tokenScanner.hasNext()) { return ""; } final String token = tokenScanner.next(); if (token.equals("exit")) { alive = false; } else if (token.equals("help")) { help(); } else if (token.equals("sign-in")) { if (!tokenScanner.hasNext()) { System.out.println("ERROR: No user name supplied."); } else { signInUser(tokenScanner.nextLine().trim()); // Automatically create the bot user and the conversation with the bot once a user signs in if (clientContext.user.lookup(BOT_NAME) == null) // Check whether the bot and convo already exist { addUser(BOT_NAME); clientContext.conversation.startConversation(BOT_CONVO, clientContext.user.getCurrent().id); } } } else if (token.equals("sign-out")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { signOutUser(); } } else if (token.equals("current")) { showCurrent(); } else if (token.equals("u-add")) { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Username not supplied."); } else { addUser(tokenScanner.nextLine().trim()); } } else if (token.equals("u-list-all")) { showAllUsers(); } else if (token.equals("c-add")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Conversation title not supplied."); } else { final String title = tokenScanner.nextLine().trim(); clientContext.conversation.startConversation(title, clientContext.user.getCurrent().id); } } } else if (token.equals("c-list-all")) { clientContext.conversation.showAllConversations(); } else if (token.equals("c-select")) { selectConversation(lineScanner); } else if (token.equals("m-add")) { if (!clientContext.user.hasCurrent()) { System.out.println("ERROR: Not signed in."); } else if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else { if (!tokenScanner.hasNext()) { System.out.println("ERROR: Message body not supplied."); } else { final String body = tokenScanner.nextLine().trim(); clientContext.message.addMessage(clientContext.user.getCurrent().id, clientContext.conversation.getCurrentId(), body); respondAsBot(body, false); } } } else if (token.equals("m-list-all")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else { clientContext.message.showAllMessages(); } } else if (token.equals("m-next")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else if (!tokenScanner.hasNextInt()) { System.out.println("Command requires an integer message index."); } else { clientContext.message.selectMessage(tokenScanner.nextInt()); } } else if (token.equals("m-show")) { if (!clientContext.conversation.hasCurrent()) { System.out.println("ERROR: No conversation selected."); } else { final int count = (tokenScanner.hasNextInt()) ? tokenScanner.nextInt() : 1; clientContext.message.showMessages(count); } } else { System.out.format("Command not recognized: %s\n", token); System.out.format("Command line rejected: %s%s\n", token, (tokenScanner.hasNext()) ? tokenScanner.nextLine() : ""); System.out.println("Type \"help\" for help."); } tokenScanner.close(); return response; } // Sign in a user. private void signInUser(String name) { if (!clientContext.user.signInUser(name)) { System.out.println("Error: sign in failed (invalid name?)"); } } // Sign out a user. private void signOutUser() { if (!clientContext.user.signOutUser()) { System.out.println("Error: sign out failed (not signed in?)"); } } // Helper for showCurrent() - show message info. private void showCurrentMessage() { if (clientContext.conversation.currentMessageCount() == 0) { System.out.println(" -- no messages in conversation --"); } else { System.out.format(" conversation has %d messages.\n", clientContext.conversation.currentMessageCount()); if (!clientContext.message.hasCurrent()) { System.out.println(" -- no current message --"); } else { System.out.println("\nCurrent Message:"); clientContext.message.showCurrent(); } } } // Show current user, conversation, message, if any private void showCurrent() { boolean displayed = false; if (clientContext.user.hasCurrent()) { System.out.println("User:"); clientContext.user.showCurrent(); System.out.println(); displayed = true; } if (clientContext.conversation.hasCurrent()) { System.out.println("Conversation:"); clientContext.conversation.showCurrent(); showCurrentMessage(); System.out.println(); displayed = true; } if (!displayed) { System.out.println("No current user or conversation."); } } // Display current user. private void showCurrentUser() { if (clientContext.user.hasCurrent()) { clientContext.user.showCurrent(); } else { System.out.println("No current user."); } } // Display current conversation. private void showCurrentConversation() { if (clientContext.conversation.hasCurrent()) { clientContext.conversation.showCurrent(); } else { System.out.println(" No current conversation."); } } // Add a new user. private void addUser(String name) { clientContext.user.addUser(name); } // Display all users known to server. private void showAllUsers() { clientContext.user.showAllUsers(); } private void showAllTags() { clientContext.tag.showAllTags(); } public boolean handleCommand(Scanner lineScanner) { try { promptForCommand(); doOneCommand(lineScanner); } catch (Exception ex) { System.out.println("ERROR: Exception during command processing. Check log for details."); LOG.error(ex, "Exception during command processing"); } // "alive" may have been set to false while executing a command. Return // the result to signal if the user wants to keep going. return alive; } public void selectConversation(Scanner lineScanner) { clientContext.conversation.updateAllConversations(false); final int selectionSize = clientContext.conversation.conversationsCount(); System.out.format("Selection contains %d entries.\n", selectionSize); final ConversationSummary previous = clientContext.conversation.getCurrent(); ConversationSummary newCurrent = null; if (selectionSize == 0) { System.out.println("Nothing to select."); } else { final ListNavigator<ConversationSummary> navigator = new ListNavigator<ConversationSummary>( clientContext.conversation.getConversationSummaries(), lineScanner, PAGE_SIZE); if (navigator.chooseFromList()) { newCurrent = navigator.getSelectedChoice(); clientContext.message.resetCurrent(newCurrent != previous); System.out.format("OK. Conversation \"%s\" selected.\n", newCurrent.title); } else { System.out.println("OK. Current Conversation is unchanged."); } } if (newCurrent != previous) { clientContext.conversation.setCurrent(newCurrent); clientContext.conversation.updateAllConversations(true); } } private void respondAsBot(String body, boolean print) { // Check if the current conversation is with the bot and respond accordingly final ConversationSummary current = clientContext.conversation.getCurrent(); if (!current.title.equals(BOT_CONVO)) { return; } final User bot = clientContext.user.lookup(BOT_NAME); if (responseMap.size() == 0) initializeResponseMap(); response = capitalizeFirstLetter(chooseBotMessage(body)); if (print) // Print out the bot's response if this isn't the test System.out.println(response); // Create a pair for the map if it's not already there addNewUserPair(response); // Send the String returned by the method back as the bot response clientContext.message.addMessage(bot.id, clientContext.conversation.getCurrentId(), response); } private void initializeResponseMap() { String curUser = clientContext.user.getCurrent().name; List<String> mappedResps; responseMap.put("how are you?", "I'm good, " + curUser + ", thanks. How are you?"); responseMap.put("what's up?", "Just hanging out! What about you?"); responseMap.put("what are you doing?", "Just hanging out! What about you?"); responseMap.put("it's nice to meet you!", "It's nice to meet you too, " + curUser); responseMap.put("it's great to meet you!", "It's great to meet you too, " + curUser); responseMap.put("it's a pleasure to meet you!", "It's a pleasure to meet you too, " + curUser); responseMap.put("good-bye", "Bye " + curUser + "!"); responseMap.put("see ya", "Bye " + curUser + "!"); responseMap.put("i'm bored", "Yeah, me too"); responseMap.put("what?", "What do you mean, 'What?'"); responseMap.put("how's life?", "Oh, you know. Same old"); responseMap.put("what are your hobbies?", "Sleeping, reading, chatting with cool kids like you, etc."); responseMap.put("what do you like to do?", "Sleeping, reading, chatting with cool kids like you, etc."); responseMap.put("you said that already", "Whoops, sorry - I'm forgetful like that!"); responseMap.put("you already told me", "Whoops, sorry - I'm forgetful like that!"); responseMap.put("what are you talking about?", "Huh? What are YOU talking about?"); responseMap.put("that was random", "Yeah, sorry...I'm kind of random sometimes"); } private String chooseBotMessage(String body) { int randResponse; // Initialize the adjustment booleans SWITCH_NAME = true; SWITCH_YOUTOO = false; // Split the user's message by sentence/phrase and store the results Set<String> mostRecent = splitMessage(body); String curUser = clientContext.user.getCurrent().name; // Send a generic response if this is the beginning of the conversation if (clientContext.conversation.currentMessageCount() <= 1) { return response = String.format("Hey %s! I'm the Bot :P", curUser); } else if (response.equals(String.format("I'm good, %s, thanks. How are you?", curUser))) { return response = HOWRU_RESP; } // Check for several standard messages else if (mostRecentContainsSubstring(mostRecent)) { return response; } else if (checkPhrasesForOnlineScript(mostRecent)) { /* Return a random response from the script list that one of the user's phrases is mapped to */ randResponse = generator.nextInt(scriptMap.get(MATCHED_PHRASE).size()); return response = scriptMap.get(MATCHED_PHRASE).get(randResponse); } // Respond with a "you too" if possible else if (userPhraseList.get(userPhraseList.size() - 1).equals(response) == false && canAddYouToo(userPhraseList.get(userPhraseList.size() - 1))) { SWITCH_YOUTOO = true; return response = adjustMessages(userPhraseList.get(userPhraseList.size() - 1), curUser); } // Let the user know that the bot doesn't have enough phrases to pick from else if (userPhraseList.size() <= 1 || clientContext.conversation.currentMessageCount() > 4 && userPhraseList.size() == 2) { return response = NO_MSGS; } else { // Check if any of the last message's phrases have been mapped already for (String key : mostRecent) { if (userPhraseMap.containsKey(key)) { // If so, choose and return a random phrase from the mapped responses with adjustments made randResponse = generator.nextInt(userPhraseMap.get(key).size()); return response = adjustMessages(userPhraseMap.get(key).get(randResponse), curUser); } } // Otherwise choose a phrase randomly from the listed responses randResponse = generator.nextInt(userPhraseList.size()); /* Keep refreshing the return phrase until it's not a parrot of the most recent message or response */ while (mostRecent.contains(userPhraseList.get(randResponse)) || response.toLowerCase().equals(userPhraseList.get(randResponse))) { randResponse = generator.nextInt(userPhraseList.size()); } return response = adjustMessages(userPhraseList.get(randResponse), curUser); } } // Instantiate an empty list as the value for the given response in the user map. private void addNewUserPair(String response) { if (response.length() >= 1 && !userPhraseMap.containsKey(response)) { ArrayList<String> blankList = new ArrayList<String>(); userPhraseMap.put(response, blankList); } } // Instantiate an empty list as the value for the given phrase in the script map. private void addNewScriptPair(String phrase) { if (!scriptMap.containsKey(phrase)) { ArrayList<String> blankList = new ArrayList<String>(); scriptMap.put(phrase, blankList); } } private Set<String> splitMessage(String body) { Set<String> sentences = new HashSet<String>(); for (String sentence : body.split("(?<=[!\\?\\.])")) { // Split the user message by punctuation if (sentence.trim().length() > 1) { // Avoid phrases of length 1, which are likely meaningless sentences.add(sentence.trim()); // Store the trimmed sentences in a set } } for (String trimmed : sentences) { // Don't add anything that matches a general greeting if (!((StringUtils.getLevenshteinDistance(trimmed, "How are you?")) <= "How are you?".length() / 3.0 || trimmed.contains("hello") || trimmed.contains("Hello"))) { if (!(userPhraseSet.contains(trimmed + ".") || userPhraseSet.contains(trimmed + "!") || userPhraseSet.contains(trimmed + "?"))) { // This will check for verbatim duplicates on its own and add phrases accordingly if (userPhraseSet.add(trimmed)) { // Also add this phrase to the list, which will be used for random access userPhraseList.add(trimmed); } } // Finally, also map this phrase to the previous response, which hasn't been updated yet. if (response.length() >= 1) { userPhraseMap.get(response).add(trimmed); } } } return sentences; } private String adjustMessages(String phrase, String curUser) { /* Replace instances of the user mentioning the bot with the user's name, as well as instances of the user mentioning himself/herself with the bot's name */ if (SWITCH_NAME) { phrase = phrase.replaceAll(curUser, "TEMPSTRING"); phrase = phrase.replaceAll(BOT_NAME.toLowerCase(), curUser).replaceAll(BOT_NAME, curUser); phrase = phrase.replaceAll("TEMPSTRING", "Bot"); } // Change a "you" to a "you too" in response if (SWITCH_YOUTOO) { phrase = new StringBuilder(phrase).insert(phrase.lastIndexOf("you") + 3, " too").toString(); } return phrase; } private boolean canAddYouToo(String phrase) { int youIndex = phrase.lastIndexOf("you"); /* Return whether the sentence structure makes sense for a "too" to be added. This is determined by checking whether "you" comes near the end of the phrase, but is not part of a question. */ return (youIndex > 0 && youIndex >= phrase.length() - 4 && phrase.charAt(phrase.length() - 1) != '?'); } private boolean mostRecentContainsSubstring(Set<String> mostRecent) { int distance; int randResponse; double bestPercent = 1; String bestResponse = ""; // Check the prewritten responses for (String key : responseMap.keySet()) { for (String phrase : mostRecent) { // Check for a match between user phrase and stored phrases using edit distance if ((!phraseReferencesSelf(phrase) && (distance = StringUtils.getLevenshteinDistance(key, phrase)) <= phrase.length() / 3.0)) { // If a match is found, record the percent match and response and move on if ((double) distance / key.length() < bestPercent) { bestPercent = (double) distance / key.length(); bestResponse = responseMap.get(key); } } else if (phrase.toLowerCase().contains(key) && phrase.length() <= key.length() * 1.3) { bestResponse = responseMap.get(key); } } } // Check the responses gained from online movie scripts for (String key : scriptMap.keySet()) { for (String phrase : mostRecent) { // Check for a match between user phrase and stored phrases using edit distance if ((!phraseReferencesSelf(phrase) && (distance = StringUtils.getLevenshteinDistance(key, phrase)) <= phrase.length() / 3.0)) { // If a match is found, record the percent match and response and move on if ((double) distance / key.length() < bestPercent) { bestPercent = (double) distance / key.length(); randResponse = generator.nextInt(scriptMap.get(key).size()); bestResponse = scriptMap.get(key).get(randResponse); } } else if (phrase.toLowerCase().contains(key)) { randResponse = generator.nextInt(scriptMap.get(key).size()); bestResponse = scriptMap.get(key).get(randResponse); } } } if (bestResponse.length() == 0) // Return false if no match gets made return false; response = bestResponse; return true; } private static boolean phraseReferencesSelf(String phrase) { phrase = phrase.toLowerCase(); if (phrase.substring(0, 2).equals("i ") || phrase.contains(" i ")) { return true; } return false; } private boolean checkPhrasesForOnlineScript(Set<String> mostRecent) { Document doc; String url; boolean found = false; // Test each phrase sent to see whether a script containing it can be found for (String phrase : mostRecent) { phrase = phrase.toLowerCase().trim(); if (phrase.length() < MIN_LENGTH) { // Skip the shorter, more generic phrases continue; } // Construct the Google search query for this phrase url = String.format(FORMAT_STR, phrase.replace(' ', '+')); List<String> links = findScript(url); // Look for scripts in the search results if (links.size() == 0) { continue; } /* If any scripts were found, iterate through all of them to see if a suitable response can be found */ MATCHED_PHRASE = phrase; addNewScriptPair(MATCHED_PHRASE); for (String link : links) { if (link.contains("script-o-rama")) { if (parseScript(link, phrase, false)) { found = true; } } else { if (parseScript(link, phrase, true)) { found = true; } } } } return found; // Return whether any scripts could be found for any phrase } private List<String> findScript(String url) { List<String> elemLinks = new ArrayList<String>(); try { Document doc = Jsoup.connect(url).get(); // Make the request String elemLink, elemText; // Parse the search results Elements links = doc.select("a[href]"); for (Element link : links) { elemLink = link.attr("href"); elemText = link.text(); /* Check if any scripts for a movie in this Google search were found. If so, add them to the links list */ if ((elemLink.contains("script-o-rama") || elemLink.contains("springfieldspringfield")) && !(elemText.equals("Cached") || elemText.equals("Similar"))) { elemLinks.add(elemLink); } } } catch (IOException e) { e.printStackTrace(); } return elemLinks; // Return an empty string to indicate failure } private boolean parseScript(String link, String phrase, boolean springfield) { String[] script; try { Document doc = Jsoup.connect(link).get(); /* If the script was retrieved from the Springfield website, the lines must be split up using the <br> tag instead of new line characters */ if (springfield) { String temp = Jsoup.parse(doc.html().replaceAll("(?i)<br[^>]*>", "br2n")).text(); script = mergeScriptSentences(temp.split("br2n")); } else { script = mergeScriptSentences(doc.body().text().split("\n")); } /* Search for a line containing the phrase. Once one is found, determine the best response and return accordingly. In some cases, this will mean continuing to search for a later match */ for (int lineNum = 0; lineNum < script.length; lineNum++) { script[lineNum] = script[lineNum].trim().toLowerCase(); for (String sentence : script[lineNum].split("(?<=[!\\?\\.])")) { if (sentence.contains(phrase) || StringUtils.getLevenshteinDistance(sentence, phrase) <= phrase.length() / 3.0) { if (findNextScriptResponse(lineNum, phrase, script)) { return true; } } } } } catch (IOException e) { e.printStackTrace(); } return false; // Return false if no line containing the phrase was found } private String[] mergeScriptSentences(String[] script) { List<String> tempScript = new ArrayList<String>(); int lineNum = 0; String mergedLine; char last = ' '; while (lineNum < script.length) { mergedLine = ""; script[lineNum] = adjustScriptLine(script[lineNum].trim()); /* Loop until a line ending in punctuation comes up, all the while combining all the lines since the last punct. into one string */ last = getLastChar(script[lineNum]); while (lineNum < script.length && last != '.' && last != '?' && last != '!') { mergedLine += script[lineNum]; mergedLine += " "; lineNum = advanceToNextNonBlankLine(lineNum, script); if (lineNum < script.length) { script[lineNum] = script[lineNum].trim(); last = getLastChar(script[lineNum]); } } if (lineNum < script.length) { mergedLine += script[lineNum]; lineNum++; } tempScript.add(adjustScriptLine(mergedLine.trim())); } String[] newScript = new String[tempScript.size()]; newScript = tempScript.toArray(newScript); return newScript; } private char getLastChar(String line) { char last; try { last = line.charAt(line.length() - 1); } catch (StringIndexOutOfBoundsException exception) { last = ' '; } return last; } private String adjustScriptLine(String phrase) { // Check if this script has character names, followed by a colon, for each line if (phrase.indexOf(":") > 0 && phrase.indexOf(":") < phrase.indexOf(" ")) { phrase = phrase.substring(phrase.indexOf(":") + 1).trim(); } // Check if this script has character names in brackets for each line if (phrase.indexOf("]") > 0 && phrase.indexOf("]") < phrase.indexOf(" ")) { phrase = phrase.substring(phrase.indexOf("]") + 1).trim(); } // Check if this script has begins lines with dashes if (phrase.indexOf("-") >= 0 && phrase.indexOf("-") < phrase.indexOf(" ")) { phrase = phrase.substring(phrase.indexOf("-") + 1).trim(); } return phrase; } private boolean findNextScriptResponse(int lineNum, String phrase, String[] script) { /* Check if a line from a different character (i.e. an actual response) can be found. If not, just respond with the very next line of dialogue */ if (canFindNewCharacter(lineNum, script)) { return true; } /* Next, check if the line containing this phrase contains more wording. If it does, use the next sentence as the response */ if (lineContainsMoreSentences(script[lineNum], phrase)) { return true; } // Advance to the next non-blank line, indicating the next piece of dialogue lineNum = advanceToNextNonBlankLine(lineNum, script); // Ensure that there was a next line of dialogue if (lineNum >= script.length || StringUtils.getLevenshteinDistance(script[lineNum].trim(), phrase) < phrase.length() / 3.0) { return false; } /* Add this response found from an online script to the response map for future reference/usage */ scriptMap.get(MATCHED_PHRASE).add(adjustScriptLine(script[lineNum].trim())); return true; } private boolean canFindNewCharacter(int lineNum, String[] script) { int counter = 0; lineNum = advanceToNextNonBlankLine(lineNum, script); while (lineNum < script.length && counter <= MAX_LINES) { /* A '-' indicates a new character's line. If one is found to start a line, and that line contains at least two words, add that phrase to the script map of responses and return. */ if (script[lineNum].charAt(0) == '-' && script[lineNum].substring(1).trim().contains(" ")) { scriptMap.get(MATCHED_PHRASE).add(adjustScriptLine(script[lineNum].substring(1).trim())); return true; } counter++; lineNum = advanceToNextNonBlankLine(lineNum, script); } // Return false if no new character's line was found soon enough return false; } private boolean lineContainsMoreSentences(String line, String phrase) { int index, punctIndex; String remainingSentence, nextSentence; // Find the end of this sentence and return false if it's the last one in the line. index = line.indexOf(phrase); if (index + phrase.length() + 1 > line.length()) { return false; } remainingSentence = line.substring(index + phrase.length() + 1).trim(); punctIndex = findNextPunctuationMark(remainingSentence); if (punctIndex == -1) { return false; } nextSentence = remainingSentence.substring(punctIndex + 1).trim(); punctIndex = findNextPunctuationMark(nextSentence); // Advance to the next sentence and ensure that there is still punctuation remaining. if (nextSentence.length() == 0 || punctIndex == -1) { return false; } /* If puncuation is found, cut the string there. Else, leave the string as it is. */ if (punctIndex != -1) { nextSentence = nextSentence.substring(0, punctIndex).trim(); // Ensure that an empty string wasn't found. if (nextSentence.length() == 0) { return false; } } nextSentence = capitalizeFirstLetter(nextSentence); scriptMap.get(MATCHED_PHRASE).add(adjustScriptLine(nextSentence)); return true; } private int findNextPunctuationMark(String nextSentence) { int periodIndex, questionIndex, exclaimIndex; if (nextSentence.trim().length() == 0) { return -1; } /* Find the end of this sentence by finding the index of the very next punctuation mark and then return it */ periodIndex = nextSentence.indexOf(".") != -1 ? nextSentence.indexOf(".") : Integer.MAX_VALUE; questionIndex = nextSentence.indexOf("?") != -1 ? nextSentence.indexOf("?") : Integer.MAX_VALUE; exclaimIndex = nextSentence.indexOf("!") != -1 ? nextSentence.indexOf("!") : Integer.MAX_VALUE; return Math.min(periodIndex, Math.min(questionIndex, exclaimIndex)); } private int advanceToNextBlankLine(int lineNum, String[] script) { if (script[lineNum].trim().length() == 0) { lineNum++; } while (lineNum + 1 < script.length && script[lineNum].trim().length() > 0) { lineNum++; script[lineNum] = script[lineNum].trim(); } return lineNum; } private int advanceToNextNonBlankLine(int lineNum, String[] script) { if (script[lineNum].trim().length() > 0) { lineNum++; } while (lineNum + 1 < script.length && script[lineNum].trim().length() == 0) { lineNum++; } return lineNum; } private String capitalizeFirstLetter(String s) { if (s.length() <= 1) { return s.toUpperCase(); } return s.substring(0, 1).toUpperCase() + s.substring(1); } }