I’m developing a companion bot for dementia suffers able to record, store and integrate memories into conversation. The bot calls an xml. file for conversations which can detect contexts passing an argument via baseContext.xml to call a topic.class and dynamically reload new xml data parser and bot while Main.class continues other tasks.
However, I can’t decide if trying to reload the parser/bot into JVM is feasible or whether pausing the class threads and calling a new class/es would be the best solution, or another solution is available. Ideally I’d like to pass the new xml data into the JVM while the other classes persist.
Any help much appreciated.
Main Class (calls the bot, camera, scheduler, text history etc)
public class Main extends javax.swing.JFrame {
private static Bot bot;
public BlockingQueue<String> queue;
public Main() {
initComponents();
//get the parser going
DataParser dp = new DataParser();
//make new bot with level 0 as default and given data parser
bot = new Bot("0", dp);
//dispaly the default message
txtHistory.setText("Bot: " + bot.getMessage());
}
public Main(BlockingQueue<String>queue) {
this.queue = queue;
}
// display bot response in the text area
private static final String VOICENAME="kevin16";
private static void addBotText(String message) {
txtHistory.setText(txtHistory.getText() + "\nBot: " + message);
//turn the bot response to sound
Voice voice;
VoiceManager vm= VoiceManager.getInstance();
voice=vm.getVoice(VOICENAME);
voice.allocate();
try{
voice.speak(bot.getMessage());
}catch(Exception e){
}
voice.deallocate();
}
public static void listen (String speech) {
txtHistory.setText(txtHistory.getText() + "\nYou: " + speech + "\n");
//send the input to the bot and get bot response
The Data parser class deals with the xml loading into DOM
package bot;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class DataParser {
private Document dom;
private HashMap<String, State> states = new HashMap<String, State>();
private ArrayList<String> invalidMessages = new ArrayList();
private int invalidMessageIndex = 0;
public int stateCounter = 1000;
public String fileSource;
// default constructor
public DataParser() {
// Load the XML file and parse it
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
//get the filepath of source
String fileSource = Context.getSource();
DocumentBuilder db = dbf.newDocumentBuilder();
//parse using builder to get DOM representation of the XML file
dom = db.parse(fileSource);
// Load configuration and states from the XML file
loadConfiguration();
loadStates();
} catch (ParserConfigurationException pce) {
pce.printStackTrace();
} catch (SAXException se) {
se.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
// Load states from XML file
private void loadStates() {
// get document element object
Element docEle = dom.getDocumentElement();
// get all State node names
NodeList nl = docEle.getElementsByTagName("State");
// if node is not null and has children
if (nl != null && nl.getLength() > 0) {
// loop through all children
for (int i = 0; i < nl.getLength(); i++) {
// get state element
Element el = (Element) nl.item(i);
// get state id
String id = el.getAttribute("id");
// get all state messages
ArrayList messages = new ArrayList();
NodeList messagesNodeList = el.getElementsByTagName("message");
// if messages node is not null and has children
if (messagesNodeList != null && messagesNodeList.getLength() > 0) {
// loop through all children
for (int j = 0; j < messagesNodeList.getLength(); j++) {
// get current message element
Element elmsg = (Element) messagesNodeList.item(j);
// append message node value to the messages list
messages.add(elmsg.getFirstChild().getNodeValue());
}
}
// get keywords in the current state
ArrayList keywords = getKeywords(el);
// construct a new State object
State state = new State(id, messages, keywords);
stateCounter ++;
// add the state to the states hashmap
states.put(id, state);
}
}
}
// get state object by id
public State getState(String id) {
return states.get(id);
}
// create a new state
public void addState(State state){
states.put(state.getId(), state);
stateCounter++;
}
// get all keywords in an State tag
public ArrayList getKeywords(Element ele) {
// construct keywords arraylist
ArrayList keywords = new ArrayList();
// get all nodes by keyword tag name
NodeList nl = ele.getElementsByTagName("keyword");
// if the tag is not null and has children
if (nl != null && nl.getLength() > 0) {
// loop through all the children
for (int i = 0; i < nl.getLength(); i++) {
//get the keyword element
Element el = (Element) nl.item(i);
// find the keyword target, classname and argument attributes
String wordTag = el.getFirstChild().getNodeValue();
String target = el.getAttribute("target");
String className = el.getAttribute("className");
String arg = el.getAttribute("arg");
String variable = el.getAttribute("variable");
int points = 0;
try{
points = Integer.valueOf(el.getAttribute("points"));
}catch (Exception e){
}
String learn = el.getAttribute("learn");
// split keyword by comma
String[] words = wordTag.split(",");
// loop through all words
for (String word : words) {
// trim the word to remove spaces
word = word.trim();
// construct a new keyword
Keyword keyword = new Keyword(word, target, className, arg, variable, points, learn );
// add the keyword to keywords array list
keywords.add(keyword);
}
}
}
// return all the keywords in the given node
return keywords;
}
// returns one of the invalid messages and move the index to the next message
public String getInvalidAnswer() {
// get current answer
String answer = invalidMessages.get(invalidMessageIndex);
// increase the index, if it is end of messages, reset the index to 0
invalidMessageIndex++;
if (invalidMessageIndex >= invalidMessages.size()) {
invalidMessageIndex = 0;
}
return answer;
}
// load cofig tags from data xml file
private void loadConfiguration() {
// get document element
Element docEle = dom.getDocumentElement();
// get all node names for invalid messages
NodeList node = docEle.getElementsByTagName("InvalidMessages");
// get all message nodes inside invalid messages node
NodeList nl = ((Element) node.item(0)).getElementsByTagName("message");
// if node is not null and has children
if (nl != null && nl.getLength() > 0) {
// loop through all children
for (int i = 0; i < nl.getLength(); i++) {
// get message node
Element el = (Element) nl.item(i);
// get message and add it to invalid messages array
String message = el.getFirstChild().getNodeValue();
invalidMessages.add(message);
}
}
}
}
The bot class manages conversation and calls specialised classes.
package bot;
import smallTalk.Morning;
import smallTalk.Afternoon;
import smallTalk.Evening;
import smallTalk.Night;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Bot {
// Store all regular expression matches
private HashMap<String,String> dictionary;
// Default state to start the bot
String level = "0";
DataParser parser;
// default constructor
public Bot(String level, DataParser parser) {
dictionary = new HashMap<String,String>();
this.level = level;
this.parser = parser;
}
// get current state message
public String getMessage() {
State state = parser.getState(level);
return replaceMatches(state.getMessage()).trim();
}
// send user message to the bot and get the response
public String send(String message) {
String response = "";
State state = parser.getState(level);
// end of the tree
if (state.getKeywords().isEmpty()) {
this.level = "0";
}
// match the keyword with given message
Keyword match = parse(message, state.getKeywords());
// if no keyword is matched, display one of the invalid answers
if (match == null) {
response = parser.getInvalidAnswer();
} else {
// if match classname is provided, check to get the dynamic response
if (match.className.length() > 0) {
// check for Weather dynamic response
if (match.className.equals("Weather")) {
Weather weather = new Weather();
response = weather.getResponse(match.arg);
this.level = "0";
}
// check for News dynamic response
else if (match.className.equals("News")) {
News news = new News();
response = news.getResponse(match.arg);
this.level = "0";
}
else if (match.className.equals("Morning")) {
Morning morning = new Morning();
morning.wakeup();
}
else if (match.className.equals("Afternoon")) {
Afternoon afternoon = new Afternoon();
afternoon.midday();
}
else if (match.className.equals("Evening")) {
Evening evening = new Evening();
evening.dinner();
}
else if (match.className.equals("Night")) {
Night night = new Night();
night.late();
}
// check for Topic dynamic response
else if (match.className.equals("Topic")) {
Topic topic = new Topic();
topic.getTopic(match.arg);
}
} else {
// get the new state and return the new message
if (response.length() == 0) {
this.level = match.target;
state = parser.getState(level);
// if it is end of the tree
if (state.getKeywords().isEmpty()) {
response = this.getMessage();
this.level = "0";
}
}
}
}
return response;
}
// parse the given text to find best match in the keywords
private Keyword parse(String text, ArrayList<Keyword> keylist) {
// set the default match to none
int bestMatch = -1;
Keyword match = null;
// loop through keywords
for (int i = 0; i < keylist.size(); i++) {
// get number of matches of the keyword with given text
int matches = getMatches(text, keylist.get(i));
// if match is better than best match, replace it
if (matches > -1 && matches > bestMatch) {
match = keylist.get(i);
bestMatch = matches;
}
}
// add best answers regex variable value into the dictionary for future reference
if (match != null){
if(match.learn.length() > 0 ){
// get training data keyword and description
String subject = dictionary.get(match.learn);
String result = match.variableValue;
// create a new state for new trained data
ArrayList<String> messages = new ArrayList<String>();
messages.add(result);
State myState = new State(String.valueOf(parser.stateCounter),messages,new ArrayList());
parser.addState(myState);
// add the new trained keyword
Keyword keyword = new Keyword(subject, myState.getId(), "", "", "", 1, "" );
State state = parser.getState("1");
ArrayList<Keyword> keywords = state.getKeywords();
keywords.add(keyword);
}else{
if (match.variableValue.length() > 0){
dictionary.put(match.variable, match.variableValue);
}
}
}
return match;
}
// get number of matches of the given keywords in the given list
private int getMatches(String text, Keyword keyword) {
// no match by default
int result = -1;
// return 0 match when keyword is *
if(keyword.keyword.equals("*")){
return keyword.points;
}
// if regex is expected
if(keyword.variable.length() > 0){
String match = Regex.match(keyword.keyword, text);
if(match.length() > 0){
keyword.variableValue = match;
return keyword.points;
}
}
String[] words = keyword.keyword.split(" ");
// loop through list of the keywords
for (String word : words) {
// if current keyword is in the text, add points
if (text.toLowerCase().indexOf(word.toLowerCase()) >= 0) {
result = result + keyword.points + 1;
} else {
// return null if one of the keywords does not exists
return -1;
}
}
return result;
}
// replace given text with variables in the dictionary
public String replaceMatches(String text){
// replace variables within dictionary in the text
for (Map.Entry<String, String> entry : dictionary.entrySet()) {
text = text.replaceAll("\\["+entry.getKey() + "\\]", entry.getValue());
}
// remove empty variables tags
return Regex.clear(text);
}
}
The Context class passes the xml file source to data parser.
package bot;
public class Context {
public static String source = "newcontext.xml";
//method to get value of source called in dataParser
public static String getSource(){
return source;
}
//get the new topic from Topic Class and prepare to reload DataParser/Bot
public static void newSource(String currentTopic){
source = currentTopic;
}
}