/******************************************************************************
*
* Copyright 2014 Paphus Solutions Inc.
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html
*
* 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 org.botlibre;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.botlibre.api.avatar.Avatar;
import org.botlibre.api.emotion.Emotion;
import org.botlibre.api.emotion.Mood;
import org.botlibre.api.knowledge.Memory;
import org.botlibre.api.sense.Awareness;
import org.botlibre.api.sense.Sense;
import org.botlibre.api.sense.Tool;
import org.botlibre.api.thought.Mind;
import org.botlibre.api.thought.Thought;
import org.botlibre.knowledge.database.DatabaseMemory;
import org.botlibre.util.Utils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* This class represents the identity of a bot,
* and controls module registration, startup and shutdown.
* It defines a singleton that represents the system.
*/
public class Bot {
public static String PROGRAM = "Bot";
public static String VERSION = "5.0 - 2016-11-24";
public static final Level FINE = Level.FINE;
public static final Level WARNING = Level.WARNING;
public static Level DEFAULT_DEBUG_LEVEL = Level.INFO;
public static final Level[] LEVELS = {Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, Level.FINE, Level.FINER, Level.FINEST, Level.ALL};
public static String CONFIG_FILE = "config.xml";
public static int MAX_CACHE = 100000;
public static int MIN_CACHE = 10000;
public static int POOL_SIZE = 20;
private static ConcurrentMap<String, Bot> instances = new ConcurrentHashMap<String, Bot>();
private static Queue<String> instancesQueue = new ConcurrentLinkedQueue<String>();
public static Bot systemCache;
public Bot parent;
private Memory memory;
private Mind mind;
private Mood mood;
private Avatar avatar;
private Awareness awareness;
private boolean filterProfanity = true;
private String name;
private ActiveState state = ActiveState.INIT;
public enum ActiveState {INIT, ACTIVE, POOLED, SHUTDOWN}
private Set<LogListener> logListeners = new HashSet<LogListener>();
private Level debugLevel = DEFAULT_DEBUG_LEVEL;
private Logger log;
private Stats stats;
static {
Logger root = Logger.getLogger("org.botlibre");
root.setUseParentHandlers(false);
StreamHandler out = new StreamHandler(System.out, new SimpleFormatter()) {
@Override
public void publish(LogRecord record) {
super.publish(record);
flush();
}
};
out.setLevel(Level.ALL);
root.addHandler(out);
try {
FileHandler file = new FileHandler("Bot.log", 5000000, 10);
file.setFormatter(new SimpleFormatter());
file.setLevel(Level.ALL);
root.addHandler(file);
} catch (IOException exception) {
exception.printStackTrace();
}
try {
FileHandler file = new FileHandler("Bot.err", 5000000, 10);
file.setFormatter(new SimpleFormatter());
file.setLevel(Level.WARNING);
root.addHandler(file);
} catch (IOException exception) {
exception.printStackTrace();
}
}
public static Bot getSystemCache() {
return systemCache;
}
public static void setSystemCache(Bot systemCache) {
Bot.systemCache = systemCache;
}
public static Bot createInstance() {
return createInstance(CONFIG_FILE, "", false);
}
public static Bot createInstance(String configFile, String memory, boolean isSchema) {
Bot Bot = new Bot();
Bot.parseConfigFile(configFile);
Bot.setState(ActiveState.ACTIVE);
Bot.log(Bot, "Creating instance:", Level.INFO, configFile, memory, isSchema);
Bot.memory().restore(memory, isSchema);
Bot.memory().awake();
Bot.mind().awake();
Bot.mood().awake();
Bot.avatar().awake();
Bot.awareness().awake();
return Bot;
}
public static Bot fastCreateInstance(String configFile, String memory, boolean isSchema) {
Bot bot = new Bot();
long start = System.currentTimeMillis();
bot.parseConfigFile(configFile);
long time = System.currentTimeMillis() - start;
if (time > 500) {
System.out.println("Connect parseConfigFile time: " + time);
}
bot.setState(ActiveState.ACTIVE);
bot.log(bot, "Fast creating instance:", Level.INFO, configFile, memory, isSchema);
start = System.currentTimeMillis();
bot.memory().fastRestore(memory, isSchema);
time = System.currentTimeMillis() - start;
if (time > 500) {
System.out.println("Connect fastRestore time: " + time);
}
start = System.currentTimeMillis();
bot.memory().awake();
bot.mind().awake();
bot.mood().awake();
bot.avatar().awake();
bot.awareness().awake();
time = System.currentTimeMillis() - start;
if (time > 500) {
System.out.println("Connect awake time: " + time);
}
return bot;
}
/**
* Return the cached instance from the pool if available, otherwise create a new instance.
*/
public static Bot createInstanceFromPool(String instanceName, boolean isSchema) {
Bot instance = instances.remove(instanceName);
if (instance != null) {
instancesQueue.remove(instanceName);
if (instance.getState() != ActiveState.POOLED) {
instance.log(instance, "Invalid instance in pool", Level.INFO);
instance = null;
} else {
instance.setState(ActiveState.ACTIVE);
instance.log(instance, "Creating instance from pool, cache size:", Level.INFO, instanceName, instance.memory().cacheSize());
}
}
if (instance == null) {
//instance = createInstance(CONFIG_FILE, instanceName);
long start = System.currentTimeMillis();
instance = fastCreateInstance(CONFIG_FILE, instanceName, isSchema);
long time = System.currentTimeMillis() - start;
instance.log(instance, "Creating new instance, time, cache size:", Level.INFO, instanceName, time, instance.memory().cacheSize());
}
return instance;
}
public Bot() {
this.log = Logger.getLogger("org.botlibre." + hashCode());
this.log.setLevel(this.debugLevel);
this.parent = systemCache;
}
/**
* Raise the statistic event.
*/
public void stat(String stat) {
if (this.stats != null) {
this.stats.stat(stat);;
}
}
public Stats getStats() {
return stats;
}
public void setStats(Stats stats) {
this.stats = stats;
}
public ActiveState getState() {
return state;
}
public void setState(ActiveState state) {
this.state = state;
}
public Bot getParent() {
return parent;
}
public void setParent(Bot parent) {
this.parent = parent;
}
public boolean getFilterProfanity() {
return filterProfanity;
}
public void setFilterProfanity(boolean filterProfanity) {
this.filterProfanity = filterProfanity;
}
/**
* Return the debugging level.
*/
public Level getDebugLevel() {
return this.debugLevel;
}
public boolean isDebug() {
return this.debugLevel.intValue() < Level.OFF.intValue();
}
public boolean isDebugFine() {
return this.debugLevel.intValue() <= Level.FINE.intValue();
}
public boolean isDebugFiner() {
return this.debugLevel.intValue() <= Level.FINER.intValue();
}
public boolean isDebugFinest() {
return this.debugLevel.intValue() <= Level.FINEST.intValue();
}
public boolean isDebugWarning() {
return this.debugLevel.intValue() <= Level.WARNING.intValue();
}
public boolean isDebugSever() {
return this.debugLevel.intValue() <= Level.SEVERE.intValue();
}
/**
* Log the message if the debug level is greater or equal to the level.
*/
public void log(Object source, String message, Level level, Object... arguments) {
try {
if (this.debugLevel.intValue() <= level.intValue()) {
for (LogListener listener : getLogListeners()) {
listener.log(source, message, level, arguments);
}
StringWriter writer = new StringWriter();
writer.write(getName()
+ " - " + Thread.currentThread()
+ " -- " + source + ":" + message);
for (Object argument : arguments) {
writer.write(" - " + argument);
}
getLog().log(level, writer.toString());
}
} catch (Exception exception) {
System.out.println(exception);
}
}
/**
* Log the message if the debug level is greater or equal to the level.
*
public void log(Object source, String message, int level) {
log(source, message, int level);
}*/
/**
* Log the exception.
*/
public void log(Object source, Throwable error) {
try {
if (isDebug()) {
for (LogListener listener : getLogListeners()) {
listener.log(error);
}
log(source, error.getMessage(), WARNING);
if (!(error instanceof BotException)) {
error.printStackTrace();
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer);
error.printStackTrace(printer);
printer.flush();
String stack = writer.toString();
log(source, stack.substring(0, Math.max(100, stack.length() - 1)), Level.WARNING);
}
}
} catch (Exception exception) {
System.out.println(exception);
}
}
/**
* Set the debugging level.
*/
public void setDebugLevel(Level level) {
this.debugLevel = level;
this.log.setLevel(level);
for (LogListener listener : getLogListeners()) {
listener.logLevelChange(level);
}
}
/**
* Return the awareness.
* The awareness defines the senses.
*/
public Awareness awareness() {
return awareness;
}
/**
* Set the awareness.
* The awareness defines the senses.
*/
public void setAwareness(Awareness awareness) {
awareness.setBot(this);
this.awareness = awareness;
}
/**
* Return the mind.
* The mind defines the thoughts.
*/
public Mind mind() {
return mind;
}
/**
* Return the mood.
* The mood defines emotional states.
*/
public Mood mood() {
return mood;
}
/**
* Return the Avatar.
* The Avatar expresses the emotional states.
*/
public Avatar avatar() {
return avatar;
}
/**
* Set the mind.
* The mind defines the thoughts.
*/
public void setMind(Mind mind) {
mind.setBot(this);
this.mind = mind;
}
/**
* Set the mood.
* The mood defines emotional states.
*/
public void setMood(Mood mood) {
mood.setBot(this);
this.mood = mood;
}
/**
* Set the Avatar.
* The Avatar expresses the emotional state.
*/
public void setAvatar(Avatar avatar) {
avatar.setBot(this);
this.avatar = avatar;
}
/**
* Return the memory.
* The memory defines the knowledge networks.
*/
public Memory memory() {
return memory;
}
/**
* Set the memory.
* The memory defines the knowledge networks.
*/
public void setMemory(Memory memory) {
memory.setBot(this);
this.memory = memory;
}
/**
* Return a Map of the property elements.
*/
protected Map<String, Object> getProperties(Element element) {
NodeList properties = element.getElementsByTagName("property");
Map<String, Object> propertyValues = new HashMap<String, Object>();
for (int index = 0; index < properties.getLength(); index++) {
Element property = (Element) properties.item(index);
propertyValues.put(property.getAttribute("name"), property.getAttribute("value"));
}
return propertyValues;
}
/**
* Parses the config.xml files using the xerces xml dom parser.
* Loads the module implementors into the Bot system.
*/
@SuppressWarnings("unchecked")
protected void parseConfigFile(String configFile) {
// Read config xml to initialize plugins.
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = factory.newDocumentBuilder();
URL url = Bot.class.getResource(configFile);
Document document = parser.parse(url.toString());
Element root = document.getDocumentElement();
// Parse and initialize memory.
Element memoryElement = (Element) root.getElementsByTagName("memory").item(0);
Element implementationClassElement = (Element) memoryElement.getElementsByTagName("implementation-class").item(0);
Class<Memory> memoryImplementor = (Class<Memory>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
setMemory(memoryImplementor.newInstance());
memory().initialize(getProperties(memoryElement));
// Parse and initialize mind.
Element mindElement = (Element) root.getElementsByTagName("mind").item(0);
implementationClassElement = (Element) mindElement.getElementsByTagName("implementation-class").item(0);
Class<Mind> mindImplementor = (Class<Mind>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
setMind(mindImplementor.newInstance());
mind().initialize(getProperties(mindElement));
NodeList thoughtElements = ((Element) mindElement.getElementsByTagName("thoughts").item(0)).getElementsByTagName("thought");
for (int index = 0; index < thoughtElements.getLength(); index++) {
Element thoughtElement = (Element) thoughtElements.item(index);
implementationClassElement = (Element) thoughtElement.getElementsByTagName("implementation-class").item(0);
Class<Thought> thoughtImplementor = (Class<Thought>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
Thought thought = thoughtImplementor.newInstance();
mind().addThought(thought);
thought.initialize(getProperties(thoughtElement));
}
// Parse and initialize mood.
Element moodElement = (Element) root.getElementsByTagName("mood").item(0);
if (moodElement != null) {
implementationClassElement = (Element) moodElement.getElementsByTagName("implementation-class").item(0);
Class<Mood> moodImplementor = (Class<Mood>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
setMood(moodImplementor.newInstance());
mood().initialize(getProperties(moodElement));
NodeList emotionElements = ((Element) moodElement.getElementsByTagName("emotions").item(0)).getElementsByTagName("emotion");
for (int index = 0; index < emotionElements.getLength(); index++) {
Element emotionElement = (Element) emotionElements.item(index);
implementationClassElement = (Element) emotionElement.getElementsByTagName("implementation-class").item(0);
Class<Emotion> emotionImplementor = (Class<Emotion>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
Emotion emotion = emotionImplementor.newInstance();
mood().addEmotion(emotion);
emotion.initialize(getProperties(emotionElement));
}
}
// Parse and initialize avatar.
Element avatarElement = (Element) root.getElementsByTagName("avatar").item(0);
implementationClassElement = (Element) avatarElement.getElementsByTagName("implementation-class").item(0);
Class<Avatar> avatarImplementor = (Class<Avatar>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
setAvatar(avatarImplementor.newInstance());
avatar().initialize(getProperties(avatarElement));
// Parse and initialize awareness.
Element awarenessElement = (Element) root.getElementsByTagName("awareness").item(0);
implementationClassElement = (Element) awarenessElement.getElementsByTagName("implementation-class").item(0);
Class<Awareness> awarenessImplementor = (Class<Awareness>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
setAwareness(awarenessImplementor.newInstance());
NodeList senseElements = ((Element) awarenessElement.getElementsByTagName("senses").item(0)).getElementsByTagName("sense");
for (int index = 0; index < senseElements.getLength(); index++) {
Element senseElement = (Element) senseElements.item(index);
implementationClassElement = (Element) senseElement.getElementsByTagName("implementation-class").item(0);
Class<Sense> senseImplementor = (Class<Sense>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
Sense sense = senseImplementor.newInstance();
awareness().addSense(sense);
sense.initialize(getProperties(senseElement));
}
NodeList toolElements = ((Element) awarenessElement.getElementsByTagName("tools").item(0)).getElementsByTagName("tool");
for (int index = 0; index < toolElements.getLength(); index++) {
Element toolElement = (Element) toolElements.item(index);
implementationClassElement = (Element) toolElement.getElementsByTagName("implementation-class").item(0);
Class<Tool> toolImplementor = (Class<Tool>)Class.forName(implementationClassElement.getFirstChild().getNodeValue());
Tool tool = toolImplementor.newInstance();
awareness().addTool(tool);
tool.initialize(getProperties(toolElement));
}
} catch (Exception exception) {
throw new InitializationException(exception);
}
}
/**
* Shutdown the system gracefully, persist memory and terminate thoughts.
*/
public synchronized void shutdown() {
if (this.state == ActiveState.SHUTDOWN) {
log(this, "Already shutdown", Level.INFO);
return;
}
this.state = ActiveState.SHUTDOWN;
log(this, "Shutting down", Level.INFO);
try {
awareness().shutdown();
mind().shutdown();
mood().shutdown();
avatar().shutdown();
memory().shutdown();
} catch (Exception exception) {
log(this, exception);
}
getLogListeners().clear();
}
/**
* Shutdown the pooled instance.
*/
public static void forceShutdown(String name) {
Bot instance = instances.remove(name);
if (instance != null) {
instancesQueue.remove(name);
instance.log(instance, "Forced shutdown", Level.WARNING);
instance.shutdown();
}
Utils.sleep(1000);
DatabaseMemory.forceShutdown(name);
}
public static void clearPool() {
while (instances.size() > 0) {
try {
String oldest = instancesQueue.remove();
Bot instance = instances.remove(oldest);
if (instance != null) {
instance.shutdown();
}
} catch (Exception exception) {
new Bot().log(instancesQueue, exception);
}
}
}
/**
* Return the instance to the pool, or shutdown if too many instances pooled.
*/
public synchronized void pool() {
if (this.state == ActiveState.SHUTDOWN) {
log(this, "Already shutdown", Level.INFO);
return;
}
if (this.state == ActiveState.POOLED) {
log(this, "Already pooled", Level.INFO);
return;
}
String name = memory().getMemoryName();
log(this, "Pooling instance", Level.INFO, name);
synchronized (memory()) {
memory().getShortTermMemory().clear();
}
if (Utils.checkLowMemory()) {
log(this, "Low memory - clearing server cache", Level.INFO);
memory().freeMemory();
}
if ((Utils.checkLowMemory(0.2) && (memory().cacheSize() > MIN_CACHE)) || memory().cacheSize() > MAX_CACHE) {
log(this, "Cache too big - clearing server cache", Level.INFO, memory().cacheSize(), MIN_CACHE, MAX_CACHE);
memory().freeMemory();
}
if (instances.containsKey(name)) {
shutdown();
return;
}
while (instances.size() >= POOL_SIZE) {
try {
String oldest = instancesQueue.remove();
Bot instance = instances.remove(oldest);
if (instance != null) {
instance.shutdown();
}
} catch (Exception exception) {
log(instancesQueue, exception);
}
}
try {
awareness().pool();
mind().pool();
mood().pool();
avatar().pool();
memory().pool();
} catch (Exception exception) {
log(this, exception);
}
// Shutdown if put not successful.
setState(ActiveState.POOLED);
setDebugLevel(Level.INFO);
if (null != instances.putIfAbsent(name, this)) {
shutdown();
return;
} else {
instancesQueue.add(name);
}
}
/**
* Return the instance's name.
* This is the real name, but defaults to the database name.
*/
public String getName() {
if (this.name == null) {
return memory().getMemoryName();
}
return this.name;
}
/**
* Set the instance's name.
* This is the real name, but defaults to the database name.
*/
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Bot(" + getName() + ")";
}
public String fullToString() {
StringWriter writer = new StringWriter();
writer.write("Bot(\n");
writer.write("\tmemory: ");
writer.write(memory().toString());
writer.write("\n");
writer.write("\tmind: ");
writer.write(mind().toString());
writer.write("\n");
for (Iterator<Thought> thoughtsIterator = mind().getThoughts().values().iterator(); thoughtsIterator.hasNext();) {
writer.write("\t\tthought: ");
writer.write(thoughtsIterator.next().toString());
writer.write("\n");
}
writer.write("\tmood: ");
writer.write(mood().toString());
writer.write("\n");
writer.write("\tavatar: ");
writer.write(avatar().toString());
writer.write("\n");
writer.write("\tawareness: ");
writer.write(awareness().toString());
writer.write("\n");
for (Iterator<Sense> sensesIterator = awareness().getSenses().values().iterator(); sensesIterator.hasNext();) {
writer.write("\t\tsense: ");
writer.write(sensesIterator.next().toString());
writer.write("\n");
}
for (Iterator<Tool> toolsIterator = awareness().getTools().values().iterator(); toolsIterator.hasNext();) {
writer.write("\t\ttool: ");
writer.write(toolsIterator.next().toString());
writer.write("\n");
}
writer.write(")");
return writer.toString();
}
public void addLogListener(LogListener listener) {
getLogListeners().add(listener);
}
public void removeLogListener(LogListener listener) {
getLogListeners().remove(listener);
}
public Set<LogListener> getLogListeners() {
return logListeners;
}
public void setLogListeners(Set<LogListener> logListeners) {
this.logListeners = logListeners;
}
public static ConcurrentMap<String, Bot> getInstances() {
return instances;
}
public Logger getLog() {
return log;
}
public void setLog(Logger log) {
this.log = log;
}
}