Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2016 the original author or authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rivescript;
import com.rivescript.lang.Java;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A RiveScript interpreter written in Java.
*
* SYNOPSIS:
*
*
*
* import com.rivescript.RiveScript;
*
* // Create a new interpreter
* RiveScript rs = new RiveScript();
*
* // Load a directory full of replies in *.rive files
* rs.loadDirectory("./replies");
*
* // Sort replies
* rs.sortReplies();
*
* // Get a reply for the user
* String reply = rs.reply("user", "Hello bot!");
*
*
*
* @author Noah Petherbridge
*/
public class RiveScript {
// Private class variables.
private boolean debug = false; // Debug mode
private int depth = 50; // Recursion depth limit
private String error = ""; // Last error text
private static Random rand = new Random(); // A random number generator
// Constant RiveScript command symbols.
private static final double RS_VERSION = 2.0; // This implements RiveScript 2.0
private static final String CMD_DEFINE = "!";
private static final String CMD_TRIGGER = "+";
private static final String CMD_PREVIOUS = "%";
private static final String CMD_REPLY = "-";
private static final String CMD_CONTINUE = "^";
private static final String CMD_REDIRECT = "@";
private static final String CMD_CONDITION = "*";
private static final String CMD_LABEL = ">";
private static final String CMD_ENDLABEL = "<";
// The topic data structure, and the "thats" data structure.
private TopicManager topics = new TopicManager();
// Bot's users' data structure.
private ClientManager clients = new ClientManager();
// Object handlers
private HashMap handlers = new HashMap<>();
private HashMap objects = new HashMap<>(); // name->language mappers
// Simpler internal data structures.
private Vector vTopics = new Vector<>(); // vector containing topic list (for quicker lookups)
private HashMap globals = new HashMap<>(); // ! global
private HashMap vars = new HashMap<>(); // ! var
private HashMap> arrays = new HashMap<>(); // ! array
private HashMap subs = new HashMap<>(); // ! sub
private String[] subs_s = null; // sorted subs
private HashMap person = new HashMap<>(); // ! person
private String[] person_s = null; // sorted persons
// The current user ID when reply() is called.
private ThreadLocal currentUser = new ThreadLocal<>();
/*-------------------------*/
/*-- Constructor Methods --*/
/*-------------------------*/
/**
* Creates a new RiveScript interpreter object, specifying the debug mode.
*
* @param debug Enable debug mode (a *lot* of stuff is printed to the terminal)
*/
public RiveScript(boolean debug) {
this.debug = debug;
// Set static debug modes.
Topic.setDebug(this.debug);
// Set the default Java macro handler.
this.setHandler("java", new Java(this));
}
/**
* Creates a new RiveScript interpreter object.
*/
public RiveScript() {
this(false);
}
/*-------------------*/
/*-- Error Methods --*/
/*-------------------*/
/**
* Returns the text of the last error message given.
*/
public String error() {
return this.error;
}
/**
* Sets the error message.
*
* @param message The new error message to set.
*/
protected boolean error(String message) {
this.error = message;
return false;
}
/*---------------------*/
/*-- Loading Methods --*/
/*---------------------*/
/**
* Loads a directory full of RiveScript documents, specifying a custom
* list of valid file extensions.
*
* @param path The path to the directory containing RiveScript documents.
* @param exts The string array containing file extensions to look for.
*/
public boolean loadDirectory(String path, String[] exts) {
say("Load directory: " + path);
// Get a directory handle.
File dh = new File(path);
// Search it for files.
for (int i = 0; i < exts.length; i++) {
// Search the directory for files of this type.
say("Searching for files of type: " + exts[i]);
final String type = exts[i];
String[] files = dh.list(new FilenameFilter() {
public boolean accept(File d, String name) {
return name.endsWith(type);
}
});
// No results?
if (files == null) {
return error("Couldn't read any files from directory " + path);
}
// Parse each file.
for (int j = 0; j < files.length; j++) {
loadFile(path + "/" + files[j]);
}
}
return true;
}
/**
* Loads a directory full of RiveScript documents ({@code .rive} files).
*
* @param path The path to the directory containing RiveScript documents.
*/
public boolean loadDirectory(String path) {
String[] exts = {".rive", ".rs"};
return this.loadDirectory(path, exts);
}
/**
* Loads a single RiveScript document.
*
* @param file The path to a RiveScript document.
*/
public boolean loadFile(String file) {
say("Load file: " + file);
// Create a file handle.
File fh = new File(file);
// Run some sanity checks on the file handle.
if (fh.exists() == false) {
return error(file + ": file not found.");
}
if (fh.isFile() == false) {
return error(file + ": not a regular file.");
}
if (fh.canRead() == false) {
return error(file + ": can't read from file.");
}
// Slurp the file's contents.
Vector lines = new Vector();
try {
FileInputStream fis = new FileInputStream(fh);
// Using buffered input stream for fast reading.
DataInputStream dis = new DataInputStream(fis);
BufferedReader br = new BufferedReader(new InputStreamReader(dis));
// Read all the lines.
String line;
while ((line = br.readLine()) != null) {
lines.add((String) line);
}
// Dispose of the resources we don't need anymore.
dis.close();
} catch (FileNotFoundException e) {
// How did this happen? We checked it earlier.
return error(file + ": file not found exception.");
} catch (IOException e) {
trace(e);
return error(file + ": IOException while reading.");
}
// Convert the vector into a string array.
String[] code = Util.Sv2s(lines);
// Send the code to the parser.
return parse(file, code);
}
/**
* Streams some RiveScript code directly into the interpreter (as a single {@link String}
* containing newlines in it).
*
* @param code The string containing all the RiveScript code.
*/
public boolean stream(String code) {
// Split the given code up into lines.
String[] lines = code.split("\n");
// Send the lines to the parser.
return parse("(streamed)", lines);
}
/**
* Streams some RiveScript code directly into the interpreter (as a {@link String} array,
* one line per item).
*
* @param code The string array containing all the lines of code.
*/
public boolean stream(String[] code) {
// The coder has already broken the lines for us!
return parse("(streamed)", code);
}
/*---------------------------*/
/*-- Configuration Methods --*/
/*---------------------------*/
/**
* Adds an {@link ObjectHandler} for a programming language to be used with RiveScript object calls.
*
* @param name The name of the programming language.
* @param handler The instance of a class that implements an ObjectHandler.
*/
public void setHandler(String name, ObjectHandler handler) {
this.handlers.put(name, handler);
}
/**
* Defines a Java {@link ObjectMacro} from your program.
*
* Because Java is a compiled language, this method must be used to create
* an object macro written in Java.
*
* @param name The name of the object macro.
* @param impl The object macro.
*/
public void setSubroutine(String name, ObjectMacro impl) {
// Is the Java handler available?
ObjectHandler handler = this.handlers.get("java");
if (handler == null) {
this.error("The Java macro handler is unavailable!");
return;
}
handler.setClass(name, impl);
this.objects.put(name, "java");
}
/**
* Sets a global variable for the interpreter (equivalent to {@code ! global}).
* Set the value to {@code null} to delete the variable.
*
* There are two special globals that require certain data types:
*
* {@code debug} is boolean-like and its value must be a string value containing
* "true", "yes", "1", "false", "no" or "0".
*
* {@code depth} is integer-like and its value must be a quoted integer like "50".
* The "depth" variable controls how many levels deep RiveScript will go when
* following reply redirections.
*
* Returns {@code true} on success, {@code false} on error.
*
* @param name The variable name.
* @param value The variable's value.
*/
public boolean setGlobal(String name, String value) {
boolean delete = false;
if (value == null || value == "") {
delete = true;
}
// Special globals
if (name.equals("debug")) {
// Debug is a boolean.
if (value.equals("true") || value.equals("1") || value.equals("yes")) {
this.debug = true;
} else if (value.equals("false") || value.equals("0") || value.equals("no") || delete) {
this.debug = false;
} else {
return error("Global variable \"debug\" needs a boolean value");
}
} else if (name.equals("depth")) {
// Depth is an integer.
try {
this.depth = Integer.parseInt(value);
} catch (NumberFormatException e) {
return error("Global variable \"depth\" needs an integer value");
}
}
// It's a user-defined global. OK.
if (delete) {
globals.remove(name);
} else {
globals.put(name, value);
}
return true;
}
/**
* Sets a bot variable for the interpreter (equivalent to {@code ! var}). A bot
* variable is data about the chatbot, like its name or favorite color.
*
* A {@code null} value will delete the variable.
*
* @param name The variable name.
* @param value The variable's value.
*/
public boolean setVariable(String name, String value) {
if (value == null || value == "") {
vars.remove(name);
} else {
vars.put(name, value);
}
return true;
}
/**
* Sets a substitution pattern (equivalent to {@code ! sub}). The user's input (and
* the bot's reply, in {@code %Previous}) get substituted using these rules.
*
* A {@code null} value for the output will delete the substitution.
*
* @param pattern The pattern to match in the message.
* @param output The text to replace it with (must be lowercase, no special characters).
*/
public boolean setSubstitution(String pattern, String output) {
if (output == null || output == "") {
subs.remove(pattern);
} else {
subs.put(pattern, output);
}
return true;
}
/**
* Sets a person substitution pattern (equivalent to {@code ! person}). Person
* substitutions swap first- and second-person pronouns, so the bot can
* safely echo the user without sounding too mechanical.
*
* A {@code null} value for the output will delete the substitution.
*
* @param pattern The pattern to match in the message.
* @param output The text to replace it with (must be lowercase, no special characters).
*/
public boolean setPersonSubstitution(String pattern, String output) {
if (output == null || output == "") {
person.remove(pattern);
} else {
person.put(pattern, output);
}
return true;
}
/**
* Sets a variable for one of the bot's users. A {@code null} value will delete a
* variable.
*
* @param user The user's id.
* @param name The name of the variable to set.
* @param value The value to set.
*/
public boolean setUservar(String user, String name, String value) {
if (value == null || value == "") {
clients.client(user).delete(name);
} else {
clients.client(user).set(name, value);
}
return true;
}
/**
* Sets -all- user vars for a user. This will replace the internal hash for
* the user. So your hash should at least contain a key/value pair for the
* user's current "topic". This could be useful if you used {@link #getUservars(String)}
* to store their entire profile somewhere and want to restore it later.
*
* @param user The user's ID.
* @param data The full hash of the user's data.
*/
public boolean setUservars(String user, HashMap data) {
// TODO: this should be handled more sanely. ;)
clients.client(user).setData(data);
return true;
}
/**
* Gets a list of all the user id's the bot knows about.
*/
public String[] getUsers() {
// Get the user list from the clients object.
return clients.listClients();
}
/**
* Returns a listing of all the uservars for a user as a {@link HashMap}.
* Returns {@code null} if the user doesn't exist.
*
* @param user The user ID to get the vars for.
*/
public HashMap getUservars(String user) {
if (clients.clientExists(user)) {
return clients.client(user).getData();
} else {
return null;
}
}
/**
* Returns a single variable from a user's profile.
*
* Returns {@code null} if the user doesn't exist. Returns the string "undefined"
* if the variable doesn't exist.
*
* @param user The user id to get data from.
* @param name The name of the variable to get.
*/
public String getUservar(String user, String name) {
if (clients.clientExists(user)) {
return clients.client(user).get(name);
} else {
return null;
}
}
/**
* Returns the current user's id from within an object macro.
*
* This is useful within a (Java) object macro to get the id of the user
* currently executing the macro (for example, to get/set variables for
* them).
*
* This function is only available during a reply context; outside of
* that it will return {@code null}.
*
* @return string user id or {@code null}.
*/
public String currentUser() {
return this.currentUser.get();
}
/**
* Returns the last trigger that the user matched.
*/
public String lastMatch(String user) {
return this.getUservar(user, "__lastmatch__");
}
/*---------------------*/
/*-- Parsing Methods --*/
/*---------------------*/
/**
* Parses RiveScript code and load it into internal memory.
*
* @param filename The file name to associate with this code (for error reporting).
* @param code The string array of all the code to parse.
*/
protected boolean parse(String filename, String[] code) {
// Track some state variables for this parsing round.
String topic = "random"; // Default topic = random
int lineno = 0;
boolean comment = false; // In a multi-line comment
boolean inobj = false; // In an object
String objName = ""; // Name of the current object
String objLang = ""; // Programming language of the object
Vector objBuff = null; // Buffer for the current object
String onTrig = ""; // Trigger we're on
String lastcmd = ""; // Last command code
String isThat = ""; // Is a %Previous trigger
// File scoped parser options.
HashMap local_options = new HashMap<>();
local_options.put("concat", "none");
// The given "code" is an array of lines, so jump right in.
for (int i = 0; i < code.length; i++) {
lineno++; // Increment the line counter.
String line = code[i];
say("Line: " + line);
// Trim the line of whitespaces.
line = line.trim();
// Are we inside an object?
if (inobj) {
if (line.startsWith("