RiveScript.java
Go to the documentation of this file.
00001 /*
00002     com.rivescript.RiveScript - The Official Java RiveScript Interpreter
00003     Copyright (C) 2010  Noah Petherbridge
00004 
00005     This program is free software; you can redistribute it and/or modify
00006     it under the terms of the GNU General Public License as published by
00007     the Free Software Foundation; either version 2 of the License, or
00008     (at your option) any later version.
00009 
00010     This program is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013     GNU General Public License for more details.
00014 
00015     You should have received a copy of the GNU General Public License along
00016     with this program; if not, write to the Free Software Foundation, Inc.,
00017     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00018  */
00019 
00020 package com.rosalfred.core.ia.rivescript;
00021 
00022 import java.io.BufferedReader;
00023 import java.io.DataInputStream;
00024 import java.io.File;
00025 import java.io.FileFilter;
00026 import java.io.FileInputStream;
00027 import java.io.FileNotFoundException;
00028 import java.io.IOException;
00029 import java.io.InputStreamReader;
00030 import java.text.Normalizer;
00031 import java.util.ArrayList;
00032 import java.util.Arrays;
00033 import java.util.HashMap;
00034 import java.util.List;
00035 import java.util.Random;
00036 import java.util.Vector;
00037 import java.util.regex.Matcher;
00038 import java.util.regex.Pattern;
00039 
00040 import com.rivescript.Client;
00041 import com.rivescript.ClientManager;
00042 import com.rivescript.Topic;
00043 import com.rivescript.TopicManager;
00044 import com.rivescript.Trigger;
00045 import com.rivescript.Util;
00046 import com.rosalfred.core.ia.rivescript.lang.Java;
00047 
00068 public class RiveScript {
00069 
00070     public static final String TYPE_PYTHON = "python";
00071     public static final String TYPE_PERL = "perl";
00072     public static final String TYPE_JAVA = "java";
00073 
00074     // Private class variables.
00075     private boolean        debug = false;        // Debug mode
00076     private int            depth = 50;           // Recursion depth limit
00077     private String         error = "";           // Last error text
00078     private static Random  rand  = new Random(); // A random number generator
00079 
00080     // Module version
00084     public static final double VERSION        = 0.02;
00085 
00086     // Constant RiveScript command symbols.
00087     private static final double RS_VERSION    = 2.0; // This implements RiveScript 2.0
00088     private static final String CMD_DEFINE    = "!";
00089     private static final String CMD_TRIGGER   = "+";
00090     private static final String CMD_PREVIOUS  = "%";
00091     private static final String CMD_REPLY     = "-";
00092     private static final String CMD_CONTINUE  = "^";
00093     private static final String CMD_REDIRECT  = "@";
00094     private static final String CMD_CONDITION = "*";
00095     private static final String CMD_LABEL     = ">";
00096     private static final String CMD_ENDLABEL  = "<";
00097 
00098     // The topic data structure, and the "thats" data structure.
00099     private TopicManager topics = new TopicManager();
00100 
00101     // Bot's users' data structure.
00102     private ClientManager clients = new ClientManager();
00103 
00104     // Object handlers
00105     private HashMap<String, ObjectHandler<BotReply>> handlers =
00106             new HashMap<String, ObjectHandler<BotReply>>();
00107     protected HashMap<String, String> objects  =
00108             new HashMap<String, String>(); // name->language mappers
00109 
00110     // Simpler internal data structures.
00111     //private ArrayList<String> vTopics =
00112     //        new ArrayList<String>(); // vector containing topic list (for quicker lookups)
00113     private HashMap<String, String>         globals  =
00114             new HashMap<String, String>();         // ! global
00115     private HashMap<String, String>         vars     =
00116             new HashMap<String, String>();         // ! var
00117     private HashMap<String, Vector<String>> arrays   =
00118             new HashMap<String, Vector<String>>(); // ! array
00119     private HashMap<String, String>         subs     =
00120             new HashMap<String, String>();         // ! sub
00121     private String[]                        subs_s   =
00122             null;                                  // sorted subs
00123     private HashMap<String, String>         person   =
00124             new HashMap<String, String>();         // ! person
00125     private String[]                        person_s =
00126             null;                                  // sorted persons
00127 
00128 
00129     private String currentUser = null;
00130 
00131     /*-------------------------*/
00132     /*-- Constructor Methods --*/
00133     /*-------------------------*/
00134 
00140     public RiveScript (boolean debug) {
00141         this.debug = debug;
00142 
00143         // Set static debug modes.
00144         Topic.setDebug(this.debug);
00145     }
00146 
00150     public RiveScript () {
00151         this(false);
00152     }
00153 
00154     /*-------------------*/
00155     /*-- Error Methods --*/
00156     /*-------------------*/
00157 
00161     public String error () {
00162         return this.error;
00163     }
00164 
00170     protected boolean error(final String message) {
00171         this.error = message;
00172         return false;
00173     }
00174 
00175     protected HashMap<String, ObjectHandler<BotReply>> getHandlers() {
00176         return this.handlers;
00177     }
00178 
00179     /*---------------------*/
00180     /*-- Loading Methods --*/
00181     /*---------------------*/
00182 
00190     public boolean loadDirectory (final String path, final String[] exts) {
00191         say("Load directory: " + path);
00192 
00193         // Get a directory handle.
00194         final File dh = new File(path);
00195 
00196         // Search it for files.
00197         for (int i = 0; i < exts.length; i++) {
00198             // Search the directory for files of this type.
00199             this.say("Searching for files of type: " + exts[i]);
00200             final String type = exts[i];
00201             final List<File> filesRive = this.getFiles(dh, type);
00202 
00203             // No results?
00204             if (filesRive == null) {
00205                 return error("Couldn't read any files from directory " + path);
00206             }
00207 
00208             // Parse each file.
00209             for (File file : filesRive) {
00210                 if (type.endsWith(TYPE_JAVA)) {
00211                     loadFileJava(file.getAbsolutePath());
00212                 } else {
00213                     loadFileRive(file.getAbsolutePath());
00214                 }
00215             }
00216         }
00217 
00218         return true;
00219     }
00220 
00221     protected List<File> getFiles(final File directory, final String type) {
00222         List<File> result = new ArrayList<File>();
00223         final List<File> directories = new ArrayList<File>();
00224 
00225         final File[] filesRive = directory.listFiles(new FileFilter() {
00226             @Override
00227             public boolean accept (final File f) {
00228                 boolean result = !f.isDirectory();
00229 
00230                 if (!result) {
00231                         directories.add(f);
00232                 }
00233 
00234                 return result && f.getName().endsWith(type);
00235             }
00236         });
00237 
00238         if (filesRive.length > 0) {
00239                 result.addAll(Arrays.asList(filesRive));
00240         }
00241 
00242         for (File file : directories) {
00243                 result.addAll(this.getFiles(file, type));
00244                 }
00245 
00246         return result;
00247     }
00248 
00254     public boolean loadDirectory (final String path) {
00255         final String[] exts = { ".rive", ".rs", "." + TYPE_JAVA };
00256         return this.loadDirectory(path, exts);
00257     }
00258 
00264     public boolean loadFileRive (final String file) {
00265         this.say("Load file: " + file);
00266 
00267         // Create a file handle.
00268         final File fh = new File(file);
00269 
00270         // Run some sanity checks on the file handle.
00271         if (!fh.exists()) {
00272             return error(file + ": file not found.");
00273         }
00274         if (!fh.isFile()) {
00275             return error(file + ": not a regular file.");
00276         }
00277         if (!fh.canRead()) {
00278             return error(file + ": can't read from file.");
00279         }
00280 
00281         String[] code;
00282         try {
00283             code = loadFile(file, fh);
00284         } catch (FileNotFoundException e) {
00285             // How did this happen? We checked it earlier.
00286             return error(file + ": file not found exception.");
00287         } catch (IOException e) {
00288             trace(e);
00289             return error(file + ": IOException while reading.");
00290         }
00291 
00292         // Send the code to the parser.
00293         return parse (file, code);
00294     }
00295 
00301     public boolean loadFileJava (String file) {
00302         say("Load file: " + file);
00303 
00304         // Create a file handle.
00305         File fh = new File(file);
00306 
00307         // Run some sanity checks on the file handle.
00308         if (fh.exists() == false) {
00309             return error(file + ": file not found.");
00310         }
00311         if (fh.isFile() == false) {
00312             return error(file + ": not a regular file.");
00313         }
00314         if (fh.canRead() == false) {
00315             return error(file + ": can't read from file.");
00316         }
00317 
00318         String[] code;
00319         try {
00320             code = loadFile(file, fh);
00321         } catch (FileNotFoundException e) {
00322             // How did this happen? We checked it earlier.
00323             return error(file + ": file not found exception.");
00324         } catch (IOException e) {
00325             trace(e);
00326             return error(file + ": IOException while reading.");
00327         }
00328 
00329         String filePath = fh.getAbsolutePath().replaceAll("."+TYPE_JAVA, "");
00330 
00331         // Send the code to the parser.
00332         Java java = (Java) this.handlers.get(TYPE_JAVA);
00333         java.onLoad(filePath, code);
00334 
00335         // Map the name to the language.
00336         if (!this.objects.containsKey(java.getLastCompiledClassName())) {
00337             this.objects.put(java.getLastCompiledClassName(), TYPE_JAVA);
00338         }
00339 
00340         return true;
00341     }
00342 
00349     private String[] loadFile(String file, File fh) throws IOException {
00350         // Slurp the file's contents.
00351         Vector<String> lines = new Vector<String>();
00352 
00353         FileInputStream fis = new FileInputStream(fh);
00354 
00355         // Using buffered input stream for fast reading.
00356         DataInputStream dis = new DataInputStream(fis);
00357         BufferedReader  br  = new BufferedReader(new InputStreamReader(dis));
00358 
00359         // Read all the lines.
00360         String line;
00361         while ((line = br.readLine()) != null) {
00362             lines.add(line);
00363         }
00364 
00365         // Dispose of the resources we don't need anymore.
00366         dis.close();
00367 
00368         // Convert the vector into a string array.
00369         String[] code = Util.Sv2s (lines);
00370         return code;
00371     }
00372 
00379     public boolean stream (String code) {
00380         // Split the given code up into lines.
00381         String[] lines = code.split("\n");
00382 
00383         // Send the lines to the parser.
00384         return parse("(streamed)", lines);
00385     }
00386 
00393     public boolean stream (String[] code) {
00394         // The coder has already broken the lines for us!
00395         return parse("(streamed)", code);
00396     }
00397 
00398     /*---------------------------*/
00399     /*-- Configuration Methods --*/
00400     /*---------------------------*/
00401 
00408     public void setHandler (String name, ObjectHandler<BotReply> handler) {
00409         this.handlers.put(name, handler);
00410     }
00411 
00430     public boolean setGlobal (String name, String value) {
00431         boolean delete = false;
00432         if (value == null || value == "<undef>") {
00433             delete = true;
00434         }
00435 
00436         // Special globals
00437         if (name.equals("debug")) {
00438             // Debug is a boolean.
00439             if (value.equals("true") ||
00440                     value.equals("1") ||
00441                     value.equals("yes")) {
00442                 this.debug = true;
00443             }
00444             else if (value.equals("false") ||
00445                     value.equals("0") ||
00446                     value.equals("no") ||
00447                     delete) {
00448                 this.debug = false;
00449             }
00450             else {
00451                 return error("Global variable \"debug\" needs a boolean value");
00452             }
00453         }
00454         else if (name.equals("depth")) {
00455             // Depth is an integer.
00456             try {
00457                 this.depth = Integer.parseInt(value);
00458             } catch (NumberFormatException e) {
00459                 return error("Global variable \"depth\" needs an integer value");
00460             }
00461         }
00462 
00463         // It's a user-defined global. OK.
00464         if (delete) {
00465             globals.remove(name);
00466         }
00467         else {
00468             globals.put(name, value);
00469         }
00470 
00471         return true;
00472     }
00473 
00483     public boolean setVariable (String name, String value) {
00484         if (value == null || value == "<undef>") {
00485             vars.remove(name);
00486         }
00487         else {
00488             vars.put(name, value);
00489         }
00490 
00491         return true;
00492     }
00493 
00503     public boolean setSubstitution (String pattern, String output) {
00504         if (output == null || output == "<undef>") {
00505             subs.remove(pattern);
00506         }
00507         else {
00508             subs.put(pattern, output);
00509         }
00510 
00511         return true;
00512     }
00513 
00524     public boolean setPersonSubstitution (String pattern, String output) {
00525         if (output == null || output == "<undef>") {
00526             person.remove(pattern);
00527         }
00528         else {
00529             person.put(pattern, output);
00530         }
00531 
00532         return true;
00533     }
00534 
00543     public boolean setUservar (String user, String name, String value) {
00544         if (value == null || value == "<undef>") {
00545             clients.client(user).delete(name);
00546         }
00547         else {
00548             clients.client(user).set(name, value);
00549         }
00550 
00551         return true;
00552     }
00553 
00563     public boolean setUservars (String user, HashMap<String, String> data) {
00564         // TODO: this should be handled more sanely. ;)
00565         clients.client(user).setData(data);
00566         return true;
00567     }
00568 
00569     public ClientManager getUservars() {
00570         return this.clients;
00571     }
00572 
00573     public void setUservars(ClientManager clients) {
00574         this.clients = clients;
00575     }
00576 
00580     public String[] getUsers () {
00581         // Get the user list from the clients object.
00582         return clients.listClients();
00583     }
00584 
00585     public String getCurrentUser() {
00586         return this.currentUser;
00587     }
00588 
00595     public HashMap<String, String> getUservars (String user) {
00596         if (clients.clientExists(user)) {
00597             return clients.client(user).getData();
00598         }
00599         else {
00600             return null;
00601         }
00602     }
00603 
00613     public String getUservar (String user, String name) {
00614         if (clients.clientExists(user)) {
00615             return clients.client(user).get(name);
00616         }
00617         else {
00618             return null;
00619         }
00620     }
00621 
00622     /*---------------------*/
00623     /*-- Parsing Methods --*/
00624     /*---------------------*/
00625 
00632     private boolean parse (String filename, String[] code) {
00633         // Track some state variables for this parsing round.
00634         String topic            = "random"; // Default topic = random
00635         int lineno              = 0;
00636         boolean comment         = false; // In a multi-line comment
00637         boolean inobj           = false; // In an object
00638         String objName          = "";    // Name of the current object
00639         String objLang          = "";    // Programming language of the object
00640         Vector<String> objBuff  = null;  // Buffer for the current object
00641         String onTrig           = "";    // Trigger we're on
00642         //String lastcmd          = "";    // Last command code
00643         String isThat           = "";    // Is a %Previous trigger
00644 
00645         // The given "code" is an array of lines, so jump right in.
00646         for (int i = 0; i < code.length; i++) {
00647             lineno++; // Increment the line counter.
00648             String line = code[i];
00649             say("Line: " + line);
00650 
00651             // Trim the line of whitespaces.
00652             line = line.trim();
00653 
00654             // Are we inside an object?
00655             if (inobj) {
00656                 if (line.startsWith("<object") || line.startsWith("< object")) { // TODO regexp
00657                     // End of the object. Did we have a handler?
00658                     if (handlers.containsKey(objLang)) {
00659                         // Yes, call the handler's onLoad function.
00660                         handlers.get(objLang).onLoad(objName, Util.Sv2s(objBuff));
00661 
00662                         // Map the name to the language.
00663                         objects.put(objName, objLang);
00664                     }
00665 
00666                     objName = "";
00667                     objLang = "";
00668                     objBuff = null;
00669                     inobj   = false;
00670                     continue;
00671                 }
00672 
00673                 // Collect the code.
00674                 objBuff.add(line);
00675                 continue;
00676             }
00677 
00678             // Look for comments.
00679             if (line.startsWith("/*")) {
00680                 // Beginning a multi-line comment.
00681                 if (line.indexOf("*/") > -1) {
00682                     // It ends on the same line.
00683                     continue;
00684                 }
00685                 comment = true;
00686             }
00687             else if (line.startsWith("/")) {
00688                 // A single line comment.
00689                 continue;
00690             }
00691             else if (line.indexOf("*/") > -1) {
00692                 // End a multi-line comment.
00693                 comment = false;
00694                 continue;
00695             }
00696             if (comment) {
00697                 continue;
00698             }
00699 
00700             // Skip any blank lines.
00701             if (line.length() < 2) {
00702                 continue;
00703             }
00704 
00705             // Separate the command from the rest of the line.
00706             String cmd = line.substring(0,1);
00707             line = line.substring(1).trim();
00708             say("\tCmd: " + cmd);
00709 
00710             // Ignore inline comments.
00711             if (line.indexOf(" // ") > -1) {
00712                 String[] split = line.split(" // ");
00713                 line = split[0];
00714             }
00715 
00716             // Reset the %Previous if this is a new +Trigger.
00717             if (cmd.equals(CMD_TRIGGER)) {
00718                 isThat = "";
00719             }
00720 
00721             // Do a look-ahead to see ^Continue and %Previous.
00722             for (int j = (i + 1); j < code.length; j++) {
00723                 // Peek ahead.
00724                 String peek = code[j].trim();
00725 
00726                 // Skip blank.
00727                 if (peek.length() == 0) {
00728                     continue;
00729                 }
00730 
00731                 // Get the command.
00732                 String peekCmd = peek.substring(0,1);
00733                 peek           = peek.substring(1).trim();
00734 
00735                 // Only continue if the lookahead line has any data.
00736                 if (peek.length() > 0) {
00737                     // The lookahead command has to be a % or a ^
00738                     if (peekCmd.equals(CMD_CONTINUE) == false &&
00739                             peekCmd.equals(CMD_PREVIOUS) == false) {
00740                         break;
00741                     }
00742 
00743                     // If the current command is a +, see if the following is a %.
00744                     if (cmd.equals(CMD_TRIGGER)) {
00745                         if (peekCmd.equals(CMD_PREVIOUS)) {
00746                             // It has a %Previous!
00747                             isThat = peek;
00748                             break;
00749                         }
00750                         else {
00751                             isThat = "";
00752                         }
00753                     }
00754 
00755                     // If the current command is a ! and the next command(s) are
00756                     // ^, we'll tack each extension on as a "line break".
00757                     if (cmd.equals(CMD_DEFINE)) {
00758                         if (peekCmd.equals(CMD_CONTINUE)) {
00759                             line += "<crlf>" + peek;
00760                         }
00761                     }
00762 
00763                     // If the current command is not a ^ and the line after is
00764                     // not a %, but the line after IS a ^, then tack it onto the
00765                     // end of the current line.
00766                     if (cmd.equals(CMD_CONTINUE) == false &&
00767                             cmd.equals(CMD_PREVIOUS) == false &&
00768                             cmd.equals(CMD_DEFINE) == false) {
00769                         if (peekCmd.equals(CMD_CONTINUE)) {
00770                             line += peek;
00771                         }
00772                         else {
00773                             break;
00774                         }
00775                     }
00776                 }
00777             }
00778 
00779             // Start handling command types.
00780             if (cmd.equals(CMD_DEFINE)) {
00781                 say("\t! DEFINE");
00782                 String[] whatis = line.split("\\s*=\\s*", 2);
00783                 String[] left   = whatis[0].split("\\s+", 2);
00784                 String type     = left[0];
00785                 String var      = "";
00786                 String value    = "";
00787                 boolean delete  = false;
00788                 if (left.length == 2)   {
00789                     var = left[1].trim().toLowerCase();
00790                 }
00791                 if (whatis.length == 2) {
00792                     value = whatis[1].trim();
00793                 }
00794 
00795                 // Remove line breaks unless this is an array.
00796                 if (!type.equals("array")) {
00797                     value = value.replaceAll("<crlf>", "");
00798                 }
00799 
00800                 // Version is the only type that doesn't have a var.
00801                 if (type.equals("version")) {
00802                     say("\tUsing RiveScript version " + value);
00803 
00804                     // Convert the value into a double, catch exceptions.
00805                     double version = 0;
00806                     try {
00807                         version = Double.valueOf(value).doubleValue();
00808                     } catch (NumberFormatException e) {
00809                         cry("RiveScript version \"" + value +
00810                                 "\" not a valid floating number",
00811                                 filename, lineno);
00812                         continue;
00813                     }
00814 
00815                     if (version > RS_VERSION) {
00816                         cry("We can't parse RiveScript v" + value +
00817                                 " documents",
00818                                 filename, lineno);
00819                         return false;
00820                     }
00821 
00822                     continue;
00823                 }
00824                 else {
00825                     // All the other types require a variable and value.
00826                     if (var.equals("")) {
00827                         cry("Missing a " + type +
00828                                 " variable name",
00829                                 filename, lineno);
00830                         continue;
00831                     }
00832                     if (value.equals("")) {
00833                         cry("Missing a " + type + " value", filename, lineno);
00834                         continue;
00835                     }
00836                     if (value.equals("<undef>")) {
00837                         // Deleting its value.
00838                         delete = true;
00839                     }
00840                 }
00841 
00842                 // Handle the variable set types.
00843                 if (type.equals("global")) {
00844                     // Is it a special global? (debug or depth or etc).
00845                     say("\tSet global " + var + " = " + value);
00846                     this.setGlobal(var, value);
00847                 }
00848                 else if (type.equals("var")) {
00849                     // Set a bot variable.
00850                     say("\tSet bot variable " + var + " = " + value);
00851                     this.setVariable(var, value);
00852                 }
00853                 else if (type.equals("array")) {
00854                     // Set an array.
00855                     say("\tSet array " + var);
00856 
00857                     // Deleting it?
00858                     if (delete) {
00859                         arrays.remove(var);
00860                         continue;
00861                     }
00862 
00863                     // Did the array have multiple lines?
00864                     String[] parts = value.split("<crlf>");
00865                     Vector<String> items = new Vector<String>();
00866                     for (int a = 0; a < parts.length; a++) {
00867                         // Split at pipes or spaces?
00868                         String[] pieces;
00869                         if (parts[a].indexOf("|") > -1) {
00870                             pieces = parts[a].split("\\|");
00871                         }
00872                         else {
00873                             pieces = parts[a].split("\\s+");
00874                         }
00875 
00876                         // Add the pieces to the final array.
00877                         for (int b = 0; b < pieces.length; b++) {
00878                             items.add(pieces[b]);
00879                         }
00880                     }
00881 
00882                     // Store this array.
00883                     arrays.put(var, items);
00884                 }
00885                 else if (type.equals("sub")) {
00886                     // Set a substitution.
00887                     say("\tSubstitution " + var + " => " + value);
00888                     this.setSubstitution(var, value);
00889                 }
00890                 else if (type.equals("person")) {
00891                     // Set a person substitution.
00892                     say("\tPerson substitution " + var + " => " + value);
00893                     this.setPersonSubstitution(var, value);
00894                 }
00895                 else {
00896                     cry("Unknown definition type \"" + type +
00897                             "\"",
00898                             filename, lineno);
00899                     continue;
00900                 }
00901             }
00902             else if (cmd.equals(CMD_LABEL)) {
00903                 // > LABEL
00904                 say("\t> LABEL");
00905                 String label[] = line.split("\\s+");
00906                 String type    = "";
00907                 String name    = "";
00908                 if (label.length >= 1) {
00909                     type = label[0].trim().toLowerCase();
00910                 }
00911                 if (label.length >= 2) {
00912                     name = label[1].trim();
00913                 }
00914 
00915                 // Handle the label types.
00916                 if (type.equals("begin")) {
00917                     // The BEGIN statement.
00918                     say("\tFound the BEGIN Statement.");
00919 
00920                     // A BEGIN is just a special topic.
00921                     type = "topic";
00922                     name = "__begin__";
00923                 }
00924                 if (type.equals("topic")) {
00925                     // Starting a new topic.
00926                     say("\tSet topic to " + name);
00927                     onTrig = "";
00928                     topic = name;
00929 
00930                     // Does this topic include or inherit another one?
00931                     if (label.length >= 3) {
00932                         final int mode_includes = 1;
00933                         final int mode_inherits = 2;
00934                         int mode = 0;
00935                         for (int a = 2; a < label.length; a++) {
00936                             if (label[a].toLowerCase().equals("includes")) {
00937                                 mode = mode_includes;
00938                             }
00939                             else if (label[a].toLowerCase().equals("inherits")) {
00940                                 mode = mode_inherits;
00941                             }
00942                             else if (mode > 0) {
00943                                 // This topic is either inherited or included.
00944                                 if (mode == mode_includes) {
00945                                     topics.topic(topic).includes(label[a]);
00946                                 }
00947                                 else if (mode == mode_inherits) {
00948                                     topics.topic(topic).inherits(label[a]);
00949                                 }
00950                             }
00951                         }
00952                     }
00953                 }
00954                 if (type.equals("object")) {
00955                     // If a field was provided, it should be the programming language.
00956                     String lang = "";
00957                     if (label.length >= 3) {
00958                         lang = label[2].toLowerCase();
00959                     }
00960 
00961                     // Only try to parse a language we support.
00962                     onTrig = "";
00963                     if (lang.length() == 0) {
00964                         cry("Trying to parse unknown programming language (assuming it's JavaScript)",
00965                                 filename, lineno);
00966                         lang = "javascript"; // Assume it's JavaScript
00967                     }
00968                     if (!handlers.containsKey(lang)) {
00969                         // We don't have a handler for this language.
00970                         say("We can't handle " + lang + " object code!");
00971                         continue;
00972                     }
00973 
00974                     // Start collecting its code!
00975                     objName = name;
00976                     objLang = lang;
00977                     objBuff = new Vector<String>();
00978                     inobj   = true;
00979                 }
00980             }
00981             else if (cmd.equals(CMD_ENDLABEL)) {
00982                 // < ENDLABEL
00983                 say("\t< ENDLABEL");
00984                 String type = line.trim().toLowerCase();
00985 
00986                 if (type.equals("begin") || type.equals("topic")) {
00987                     say("\t\tEnd topic label.");
00988                     topic = "random";
00989                 }
00990                 else if (type.equals("object")) {
00991                     say("\t\tEnd object label.");
00992                     inobj = false;
00993                 }
00994                 else {
00995                     cry("Unknown end topic type \"" + type + "\"",
00996                             filename, lineno);
00997                 }
00998             }
00999             else if (cmd.equals(CMD_TRIGGER)) {
01000                 // + TRIGGER
01001                 say("\t+ TRIGGER: " + line);
01002 
01003                 if (isThat.length() > 0) {
01004                     // This trigger had a %Previous. To prevent conflict, tag the
01005                     // trigger with the "that" text.
01006                     onTrig = line + "{previous}" + isThat;
01007                     topics.topic(topic).trigger(line).hasPrevious(true);
01008                     topics.topic(topic).addPrevious(line, isThat);
01009                 }
01010                 else {
01011                     // Set the current trigger to this.
01012                     onTrig = line;
01013                 }
01014             }
01015             else if (cmd.equals(CMD_REPLY)) {
01016                 // - REPLY
01017                 say("\t- REPLY: " + line);
01018 
01019                 // This can't come before a trigger!
01020                 if (onTrig.length() == 0) {
01021                     cry("Reply found before trigger", filename, lineno);
01022                     continue;
01023                 }
01024 
01025                 // Add the reply to the trigger.
01026                 topics.topic(topic).trigger(onTrig).addReply(line);
01027             }
01028             else if (cmd.equals(CMD_PREVIOUS)) {
01029                 // % PREVIOUS
01030                 // This was handled above.
01031             }
01032             else if (cmd.equals(CMD_CONTINUE)) {
01033                 // ^ CONTINUE
01034                 // This was handled above.
01035             }
01036             else if (cmd.equals(CMD_REDIRECT)) {
01037                 // @ REDIRECT
01038                 say("\t@ REDIRECT: " + line);
01039 
01040                 // This can't come before a trigger!
01041                 if (onTrig.length() == 0) {
01042                     cry("Redirect found before trigger", filename, lineno);
01043                     continue;
01044                 }
01045 
01046                 // Add the redirect to the trigger.
01047                 // TODO: this extends RiveScript, not compat w/ Perl yet
01048                 topics.topic(topic).trigger(onTrig).addRedirect(line);
01049             }
01050             else if (cmd.equals(CMD_CONDITION)) {
01051                 // * CONDITION
01052                 say("\t* CONDITION: " + line);
01053 
01054                 // This can't come before a trigger!
01055                 if (onTrig.length() == 0) {
01056                     cry("Redirect found before trigger", filename, lineno);
01057                     continue;
01058                 }
01059 
01060                 // Add the condition to the trigger.
01061                 topics.topic(topic).trigger(onTrig).addCondition(line);
01062             }
01063             else {
01064                 cry("Unrecognized command \"" + cmd + "\"", filename, lineno);
01065             }
01066         }
01067 
01068         return true;
01069     }
01070 
01071     /*---------------------*/
01072     /*-- Sorting Methods --*/
01073     /*---------------------*/
01074 
01079     public void sortReplies () {
01080         // We need to make sort buffers under each topic.
01081         String[] topics = this.topics.listTopics();
01082         say("There are " + topics.length + " topics to sort replies for.");
01083 
01084         // Tell the topic manager to sort its topics' replies.
01085         this.topics.sortReplies();
01086 
01087         // Sort the substitutions.
01088         subs_s = Util.sortByLength (Util.SSh2s(subs));
01089         person_s = Util.sortByLength (Util.SSh2s(person));
01090     }
01091 
01092     /*---------------------*/
01093     /*-- Reply Methods   --*/
01094     /*---------------------*/
01095 
01102     public BotReply reply (String username, String message) {
01103         say("Get reply to [" + username + "] " + message);
01104 
01105         this.currentUser = username;
01106 
01107         // Format their message first.
01108         message = formatMessage (message);
01109 
01110         // This will hold the final reply.
01111         BotReply reply = new BotReply("");
01112 
01113         // If the BEGIN statement exists, consult it first.
01114         if (topics.exists("__begin__")) {
01115             BotReply begin = this.reply (username, "request", true, 0);
01116 
01117             // OK to continue?
01118             if (begin.getReply().indexOf("{ok}") > -1) {
01119                 // Get a reply then.
01120                 reply = this.reply (username, message, false, 0);
01121                 begin.setReply(begin.getReply().replaceAll(
01122                         "\\{ok\\}", reply.getReply()));
01123                 reply.setReply(begin.getReply());
01124             }
01125 
01126             // Run final substitutions.
01127             reply = processTags (username,
01128                     clients.client(username),
01129                     message,
01130                     reply,
01131                     new ArrayList<String>(),
01132                     new ArrayList<String>(),
01133                     0);
01134         }
01135         else {
01136             // No BEGIN, just continue.
01137             reply = this.reply (username, message, false, 0);
01138         }
01139 
01140         // Save their chat history.
01141         clients.client(username).addInput(message);
01142         clients.client(username).addReply(reply.getReply()); //TODO replace by BotReply?
01143 
01144         // Return their reply.
01145         return reply;
01146     }
01147 
01156     private BotReply reply (String user, String message, boolean begin, int step) {
01157         BotReply result = new BotReply();
01158 
01159         /*-----------------------*/
01160         /*-- Collect User Info --*/
01161         /*-----------------------*/
01162 
01163         String topic            = "random";             // Default topic = random
01164         ArrayList<String> stars    = new ArrayList<String>(); // Wildcard matches
01165         ArrayList<String> botstars = new ArrayList<String>(); // Wildcards in %Previous
01166         String reply            = "";                   // The eventual reply
01167         Client profile;                                 // The user's profile object
01168 
01169         // Get the user's profile.
01170         profile = clients.client(user);
01171 
01172         // Update their topic.
01173         topic = profile.get("topic");
01174 
01175         // Avoid letting the user fall into a missing topic.
01176         if (topics.exists(topic) == false) {
01177             cry("User " + user + " was in a missing topic named \"" + topic + "\"!");
01178             topic = "random";
01179             profile.set("topic", "random");
01180         }
01181 
01182         // Avoid deep recursion.
01183         if (step > depth) {
01184             reply = "ERR: Deep Recursion Detected!";
01185             cry(reply);
01186             result.setReply(reply);
01187             return result;
01188         }
01189 
01190         // Are we in the BEGIN statement?
01191         if (begin) {
01192             // This implies the begin topic.
01193             topic = "__begin__";
01194         }
01195 
01196         /*------------------*/
01197         /*-- Find a Reply --*/
01198         /*------------------*/
01199 
01200         // Create a pointer for the matched data.
01201         Trigger matched = null;
01202         boolean foundMatch     = false;
01203         String  matchedTrigger = "";
01204 
01205         // See if there are any %previous's in this topic, or any topic related to it. This
01206         // should only be done the first time -- not during a recursive redirection.
01207         if (step == 0) {
01208             say("Looking for a %Previous");
01209             String[] allTopics = { topic };
01210             //          if (this.topics.topic(topic).includes() || this.topics.topic(topic).inherits()) {
01211             // We need to walk the topic tree.
01212             allTopics = this.topics.getTopicTree(topic, 0);
01213             //          }
01214             for (int i = 0; i < allTopics.length; i++) {
01215                 // Does this topic have a %Previous anywhere?
01216                 say("Seeing if " + allTopics[i] + " has a %Previous");
01217                 if (this.topics.topic(allTopics[i]).hasPrevious()) {
01218                     say("Topic " + allTopics[i] + " has at least one %Previous");
01219 
01220                     // Get them.
01221                     String[] previous = this.topics.topic(allTopics[i]).listPrevious();
01222                     for (int j = 0; j < previous.length; j++) {
01223                         say("Candidate: " + previous[j]);
01224 
01225                         // Try to match the bot's last reply against this.
01226                         String lastReply = formatMessage(profile.getReply(1));
01227                         String regexp    = triggerRegexp(user, profile, previous[j]);
01228                         say("Compare " + lastReply + " <=> " + previous[j] + " (" + regexp + ")");
01229 
01230                         // Does it match?
01231                         Pattern re = Pattern.compile("^" + regexp + "$");
01232                         Matcher m  = re.matcher(lastReply);
01233                         while (m.find() == true) {
01234                             say("OMFG the lastReply matches!");
01235 
01236                             // Harvest the botstars.
01237                             for (int s = 1; s <= m.groupCount(); s++) {
01238                                 say("Add botstar: " + m.group(s));
01239                                 botstars.add(m.group(s));
01240                             }
01241 
01242                             // Now see if the user matched this trigger too!
01243                             String[] candidates = this.topics.topic(allTopics[i]).listPreviousTriggers(previous[j]);
01244                             for (int k = 0; k < candidates.length; k++) {
01245                                 say("Does the user's message match " + candidates[k] + "?");
01246                                 String humanside = triggerRegexp(user, profile, candidates[k]);
01247                                 say("Compare " + message + " <=> " + candidates[k] + " (" + humanside + ")");
01248 
01249                                 Pattern reH = Pattern.compile("^" + humanside + "$");
01250                                 Matcher mH  = reH.matcher(message);
01251                                 while (mH.find() == true) {
01252                                     say("It's a match!!!");
01253 
01254                                     // Make sure it's all valid.
01255                                     String realTrigger = candidates[k] + "{previous}" + previous[j];
01256                                     if (this.topics.topic(allTopics[i]).triggerExists(realTrigger)) {
01257                                         // Seems to be! Collect the stars.
01258                                         for (int s = 1; s <= mH.groupCount(); s++) {
01259                                             say("Add star: " + mH.group(s));
01260                                             stars.add(mH.group(s));
01261                                         }
01262 
01263                                         foundMatch = true;
01264                                         matchedTrigger = candidates[k];
01265                                         matched = this.topics.topic(allTopics[i]).trigger(realTrigger);
01266                                     }
01267 
01268                                     break;
01269                                 }
01270 
01271                                 if (foundMatch) {
01272                                     break;
01273                                 }
01274                             }
01275                             if (foundMatch) {
01276                                 break;
01277                             }
01278                         }
01279                     }
01280                 }
01281             }
01282         }
01283 
01284         // Search their topic for a match to their trigger.
01285         if (foundMatch == false) {
01286             // Go through the sort buffer for their topic.
01287             String[] triggers = topics.topic(topic).listTriggers();
01288             for (int a = 0; a < triggers.length; a++) {
01289                 String trigger = triggers[a];
01290 
01291                 // Prepare the trigger for the regular expression engine.
01292                 String regexp = triggerRegexp(user, profile, trigger);
01293                 say("Try to match \"" + message + "\" against \"" + trigger + "\" (" + regexp + ")");
01294 
01295                 // Is it a match?
01296                 Pattern re = Pattern.compile("^" + regexp + "$");
01297                 Matcher m  = re.matcher(message);
01298                 if (m.find() == true) {
01299                     say("The trigger matches! Star count: " + m.groupCount());
01300 
01301                     // Harvest the stars.
01302                     int starcount = m.groupCount();
01303                     for (int s = 1; s <= starcount; s++) {
01304                         say("Add star: " + m.group(s));
01305                         stars.add(m.group(s));
01306                     }
01307 
01308                     // We found a match, but what if the trigger we matched belongs to
01309                     // an inherited topic? Check for that.
01310                     if (this.topics.topic(topic).triggerExists(trigger)) {
01311                         // No, the trigger does belong to us.
01312                         matched = this.topics.topic(topic).trigger(trigger);
01313                     }
01314                     else {
01315                         say("Trigger doesn't exist under this topic, trying to find it!");
01316                         matched = this.topics.findTriggerByInheritance(topic, trigger, 0);
01317                     }
01318 
01319                     foundMatch = true;
01320                     matchedTrigger = trigger;
01321                     break;
01322                 }
01323             }
01324         }
01325 
01326         // Store what trigger they matched on (matchedTrigger can be blank if they didn't match).
01327         profile.set("__lastmatch__", matchedTrigger);
01328 
01329         // Did they match anything?
01330         if (foundMatch) {
01331             say("They were successfully matched to a trigger!");
01332 
01333             /*---------------------------------*/
01334             /*-- Process Their Matched Reply --*/
01335             /*---------------------------------*/
01336 
01337             // Make a dummy once loop so we can break out anytime.
01338             for (int n = 0; n < 1; n++) {
01339                 // Exists?
01340                 if (matched == null) {
01341                     cry("Unknown error: they matched trigger " + matchedTrigger + ", but it doesn't exist?");
01342                     foundMatch = false;
01343                     break;
01344                 }
01345 
01346                 // Get the trigger object.
01347                 Trigger trigger = matched;
01348                 say("The trigger matched belongs to topic " + trigger.topic());
01349 
01350                 //See if there are any hard redirects.
01351                 if (matched.listRedirects() != null && matched.listRedirects().length > 0) {
01352                     say("Redirecting us to " + matched.listRedirects()[0]);
01353                     result = processTags(user, profile, message,
01354                             new BotReply(matched.listRedirects()[0]),
01355                             stars, botstars, step);
01356                     say("Pretend user said: " + result.getReply());
01357                     reply = reply(user, result.getReply(), false, step=(step+1)).getReply();
01358                     break;
01359                 }
01360 
01361                 // Check for conditions.
01362                 String[] conditions = trigger.listConditions();
01363                 if (conditions.length > 0) {
01364                     say("This trigger has some conditions!");
01365 
01366                     // See if any conditions are true.
01367                     boolean truth = false;
01368                     for (int c = 0; c < conditions.length; c++) {
01369                         // Separate the condition from the potential reply.
01370                         String[] halves = conditions[c].split("\\s*=>\\s*");
01371                         String condition = halves[0].trim();
01372                         String potreply  = halves[1].trim();
01373 
01374                         // Split up the condition.
01375                         Pattern reCond = Pattern.compile("^(.+?)\\s+(==|eq|\\!=|ne|<>|<|<=|>|>=)\\s+(.+?)$");
01376                         Matcher mCond  = reCond.matcher(condition);
01377                         while (mCond.find()) {
01378                             String left  = mCond.group(1).trim();
01379                             String eq    = mCond.group(2).trim();
01380                             String right = mCond.group(3).trim();
01381 
01382                             // Process tags on both halves.
01383                             left = processTags(user, profile, message, left, stars, botstars, step+1);
01384                             right = processTags(user, profile, message, right, stars, botstars, step+1);
01385                             say("Compare: " + left + " " + eq + " " + right);
01386 
01387                             // Defaults
01388                             if (left.length() == 0) {
01389                                 left = "undefined";
01390                             }
01391                             if (right.length() == 0) {
01392                                 right = "undefined";
01393                             }
01394 
01395                             // Validate the expression.
01396                             if (eq.equals("eq") || eq.equals("ne") || eq.equals("==") || eq.equals("!=") || eq.equals("<>")) {
01397                                 // String equality comparing.
01398                                 if ((eq.equals("eq") || eq.equals("==")) && left.equals(right)) {
01399                                     truth = true;
01400                                     break;
01401                                 }
01402                                 else if ((eq.equals("ne") || eq.equals("!=") || eq.equals("<>")) && !left.equals(right)) {
01403                                     truth = true;
01404                                     break;
01405                                 }
01406                             }
01407 
01408                             // Numeric comparing.
01409                             int lt = 0;
01410                             int rt = 0;
01411 
01412                             // Turn the two sides into numbers.
01413                             try {
01414                                 lt = Integer.parseInt(left);
01415                                 rt = Integer.parseInt(right);
01416                             } catch (NumberFormatException e) {
01417                                 // Oh well!
01418                                 break;
01419                             }
01420 
01421                             // Run the remaining equality checks.
01422                             if (eq.equals("==") || eq.equals("!=") || eq.equals("<>")) {
01423                                 // Equality checks.
01424                                 if (eq.equals("==") && lt == rt) {
01425                                     truth = true;
01426                                     break;
01427                                 }
01428                                 else if ((eq.equals("!=") || eq.equals("<>")) && lt != rt) {
01429                                     truth = true;
01430                                     break;
01431                                 }
01432                             }
01433                             else if (eq.equals("<") && lt < rt) {
01434                                 truth = true;
01435                                 break;
01436                             }
01437                             else if (eq.equals("<=") && lt <= rt) {
01438                                 truth = true;
01439                                 break;
01440                             }
01441                             else if (eq.equals(">") && lt > rt) {
01442                                 truth = true;
01443                                 break;
01444                             }
01445                             else if (eq.equals(">=") && lt >= rt) {
01446                                 truth = true;
01447                                 break;
01448                             }
01449                         }
01450 
01451                         // True condition?
01452                         if (truth) {
01453                             reply = potreply;
01454                             break;
01455                         }
01456                     }
01457                 }
01458 
01459                 // Break if we got a reply from the conditions.
01460                 if (reply.length() > 0) {
01461                     break;
01462                 }
01463 
01464                 // Return one of the replies at random. We lump any redirects in as well.
01465                 String[] redirects = trigger.listRedirects();
01466                 String[] replies   = trigger.listReplies();
01467 
01468                 // Take into account their weights.
01469                 Vector<Integer> bucket = new Vector<Integer>();
01470                 Pattern reWeight = Pattern.compile("\\{weight=(\\d+?)\\}");
01471 
01472                 // Look at weights on redirects.
01473                 for (int i = 0; i < redirects.length; i++) {
01474                     if (redirects[i].indexOf("{weight=") > -1) {
01475                         Matcher mWeight = reWeight.matcher(redirects[i]);
01476                         while (mWeight.find()) {
01477                             int weight = Integer.parseInt(mWeight.group(1));
01478 
01479                             // Add to the bucket this many times.
01480                             if (weight > 1) {
01481                                 for (int j = 0; j < weight; j++) {
01482                                     say("Trigger has a redirect (weight " + weight + "): " + redirects[i]);
01483                                     bucket.add(i);
01484                                 }
01485                             }
01486                             else {
01487                                 say("Trigger has a redirect (weight " + weight + "): " + redirects[i]);
01488                                 bucket.add(i);
01489                             }
01490 
01491                             // Only one weight is supported.
01492                             break;
01493                         }
01494                     }
01495                     else {
01496                         say("Trigger has a redirect: " + redirects[i]);
01497                         bucket.add(i);
01498                     }
01499                 }
01500 
01501                 // Look at weights on replies.
01502                 for (int i = 0; i < replies.length; i++) {
01503                     if (replies[i].indexOf("{weight=") > -1) {
01504                         Matcher mWeight = reWeight.matcher(replies[i]);
01505                         while (mWeight.find()) {
01506                             int weight = Integer.parseInt(mWeight.group(1));
01507 
01508                             // Add to the bucket this many times.
01509                             if (weight > 1) {
01510                                 for (int j = 0; j < weight; j++) {
01511                                     say("Trigger has a reply (weight " + weight + "): " + replies[i]);
01512                                     bucket.add(redirects.length + i);
01513                                 }
01514                             }
01515                             else {
01516                                 say("Trigger has a reply (weight " + weight + "): " + replies[i]);
01517                                 bucket.add(redirects.length + i);
01518                             }
01519 
01520                             // Only one weight is supported.
01521                             break;
01522                         }
01523                     }
01524                     else {
01525                         say("Trigger has a reply: " + replies[i]);
01526                         bucket.add(redirects.length + i);
01527                     }
01528                 }
01529 
01530                 // Pull a random value out.
01531                 int[] choices = Util.Iv2s(bucket);
01532                 if (choices.length > 0) {
01533                     int choice = choices [ rand.nextInt(choices.length) ];
01534                     say("Possible choices: " + choices.length + "; chosen: " + choice);
01535                     if (choice < redirects.length) {
01536                         // The choice was a redirect!
01537                         String redirect = redirects[choice].replaceAll("\\{weight=\\d+\\}","");
01538                         say("Chosen a redirect to " + redirect + "!");
01539                         reply = reply(user, redirect, begin, step+1).getReply();
01540                     }
01541                     else {
01542                         // The choice was a reply!
01543                         choice -= redirects.length;
01544                         if (choice < replies.length) {
01545                             say("Chosen a reply: " + replies[choice]);
01546                             reply = replies[choice];
01547                         }
01548                     }
01549                 }
01550             }
01551         }
01552 
01553         // Still no reply?
01554         if (!foundMatch) {
01555             reply = "ERR: No Reply Matched";
01556         }
01557         else if (reply.length() == 0) {
01558             reply = "ERR: No Reply Found";
01559         }
01560 
01561         say("Final reply: " + reply);
01562 
01563         // Special tag processing for the BEGIN statement.
01564         if (begin) {
01565             // The BEGIN block may have {topic} or <set> tags and that's all.
01566             // <set> tag
01567             if (reply.indexOf("<set") > -1) {
01568                 Pattern reSet = Pattern.compile("<set (.+?)=(.+?)>");
01569                 Matcher mSet  = reSet.matcher(reply);
01570                 while (mSet.find()) {
01571                     String tag   = mSet.group(0);
01572                     String var   = mSet.group(1);
01573                     String value = mSet.group(2);
01574 
01575                     // Set the uservar.
01576                     profile.set(var, value);
01577                     reply = reply.replace(tag, "");
01578                 }
01579             }
01580 
01581             // {topic} tag
01582             if (reply.indexOf("{topic=") > -1) {
01583                 Pattern reTopic = Pattern.compile("\\{topic=(.+?)\\}");
01584                 Matcher mTopic  = reTopic.matcher(reply);
01585                 while (mTopic.find()) {
01586                     String tag = mTopic.group(0);
01587                     topic      = mTopic.group(1);
01588                     say("Set user's topic to: " + topic);
01589                     profile.set("topic", topic);
01590                     reply = reply.replace(tag, "");
01591                 }
01592             }
01593 
01594             result.setReply(reply);
01595         }
01596         else {
01597             // Process tags.
01598             result.setReply(reply);
01599             result = processTags (user, profile, message, result, stars, botstars, step);
01600         }
01601 
01602         return result;
01603     }
01604 
01611     private String triggerRegexp (String user, Client profile, String trigger) {
01612         // If the trigger is simply '*', it needs to become (.*?) so it catches the empty string.
01613         String regexp = trigger.replaceAll("^\\*$", "<zerowidthstar>");
01614 
01615         // Simple regexps are simple.
01616         regexp = regexp.replaceAll("\\*", "(.+?)");             // *  ->  (.+?)
01617         regexp = regexp.replaceAll("#",   "(\\\\d+?)");         // #  ->  (\d+?)
01618         regexp = regexp.replaceAll("_",   "(\\\\w+?)");     // _  ->  ([A-Za-z ]+?)
01619         regexp = regexp.replaceAll("\\{weight=\\d+\\}", "");    // Remove {weight} tags
01620         regexp = regexp.replaceAll("<zerowidthstar>", "(.*?)"); // *  ->  (.*?)
01621 
01622         // Handle optionals.
01623         if (regexp.indexOf("[") > -1) {
01624             Pattern reOpts = Pattern.compile("\\s*\\[(.+?)\\]\\s*");
01625             Matcher mOpts  = reOpts.matcher(regexp);
01626             while (mOpts.find() == true) {
01627                 String optional = mOpts.group(0);
01628                 String contents = mOpts.group(1);
01629 
01630                 // Split them at the pipes.
01631                 String[] parts = contents.split("\\|");
01632 
01633                 // Construct a regexp part.
01634                 StringBuffer re = new StringBuffer();
01635                 for (int i = 0; i < parts.length; i++) {
01636                     // We want: \s*part\s*
01637                     re.append("\\s*" + parts[i] + "\\s*");
01638                     if (i < parts.length - 1) {
01639                         re.append("|");
01640                     }
01641                 }
01642                 String pipes = re.toString();
01643 
01644                 // If this optional had a star or anything in it, e.g. [*],
01645                 // make it non-matching.
01646                 pipes = pipes.replaceAll("\\(.+?\\)", "(?:.+?)");
01647                 pipes = pipes.replaceAll("\\(\\d+?\\)", "(?:\\\\d+?");
01648                 pipes = pipes.replaceAll("\\(\\w+?\\)", "(?:\\\\w+?)");
01649 
01650                 // Put the new text in.
01651                 pipes = "(?:" + pipes + "|\\s*)";
01652                 regexp = regexp.replace(optional, pipes);
01653             }
01654         }
01655 
01656         // Make \w more accurate for our purposes.
01657         regexp = regexp.replaceAll("\\\\w", "[a-z ]");
01658 
01659         // Filter in arrays.
01660         if (regexp.indexOf("@") > -1) {
01661             // Match the array's name.
01662             Pattern reArray = Pattern.compile("\\@(.+?)\\b");
01663             Matcher mArray  = reArray.matcher(regexp);
01664             while (mArray.find() == true) {
01665                 String array = mArray.group(0);
01666                 String name  = mArray.group(1);
01667 
01668                 // Do we have an array by this name?
01669                 if (arrays.containsKey(name)) {
01670                     String[] values = Util.Sv2s(arrays.get(name));
01671                     StringBuffer joined = new StringBuffer();
01672 
01673                     // Join the array.
01674                     for (int i = 0; i < values.length; i++) {
01675                         joined.append(values[i]);
01676                         if (i < values.length - 1) {
01677                             joined.append("|");
01678                         }
01679                     }
01680 
01681                     // Final contents...
01682                     String rep = "(?:" + joined.toString() + ")";
01683                     regexp = regexp.replace(array, rep);
01684                 }
01685                 else {
01686                     // No array by this name.
01687                     regexp = regexp.replace(array, "");
01688                 }
01689             }
01690         }
01691 
01692         // Filter in bot variables.
01693         if (regexp.indexOf("<bot") > -1) {
01694             Pattern reBot = Pattern.compile("<bot (.+?)>");
01695             Matcher mBot  = reBot.matcher(regexp);
01696             while (mBot.find()) {
01697                 String tag = mBot.group(0);
01698                 String var = mBot.group(1);
01699                 String value = vars.get(var).toLowerCase().replace("[^a-z0-9 ]+","");
01700 
01701                 // Have this?
01702                 if (vars.containsKey(var)) {
01703                     regexp = regexp.replace(tag, value);
01704                 }
01705                 else {
01706                     regexp = regexp.replace(tag, "undefined");
01707                 }
01708             }
01709         }
01710 
01711         // Filter in user variables.
01712         if (regexp.indexOf("<get") > -1) {
01713             Pattern reGet = Pattern.compile("<get (.+?)>");
01714             Matcher mGet  = reGet.matcher(regexp);
01715             while (mGet.find()) {
01716                 String tag = mGet.group(0);
01717                 String var = mGet.group(1);
01718                 String value = profile.get(var).toLowerCase().replaceAll("[^a-z0-9 ]+","");
01719 
01720                 // Have this?
01721                 regexp = regexp.replace(tag, value);
01722             }
01723         }
01724 
01725         // Input and reply tags.
01726         regexp = regexp.replaceAll("<input>", "<input1>");
01727         regexp = regexp.replaceAll("<reply>", "<reply1>");
01728         if (regexp.indexOf("<input") > -1) {
01729             Pattern reInput = Pattern.compile("<input([0-9])>");
01730             Matcher mInput  = reInput.matcher(regexp);
01731             while (mInput.find()) {
01732                 String tag   = mInput.group(0);
01733                 int    index = Integer.parseInt(mInput.group(1));
01734                 String text  = profile.getInput(index).toLowerCase().replaceAll("[^a-z0-9 ]+","");
01735                 regexp       = regexp.replace(tag, text);
01736             }
01737         }
01738         if (regexp.indexOf("<reply") > -1) {
01739             Pattern reReply = Pattern.compile("<reply([0-9])>");
01740             Matcher mReply  = reReply.matcher(regexp);
01741             while (mReply.find()) {
01742                 String tag   = mReply.group(0);
01743                 int    index = Integer.parseInt(mReply.group(1));
01744                 String text  = profile.getReply(index).toLowerCase().replaceAll("[^a-z0-9 ]+","");
01745                 regexp       = regexp.replace(tag, text);
01746             }
01747         }
01748 
01749         return regexp;
01750     }
01751 
01752     private String processTags(String user,
01753             Client profile,
01754             String message,
01755             String reply,
01756             ArrayList<String> vstars,
01757             ArrayList<String> vbotstars,
01758             int step) {
01759         return this.processTags(user, profile, message, new BotReply(reply),
01760                 vstars, vbotstars, step).getReply();
01761     }
01762 
01774     private BotReply processTags (String user,
01775             Client profile,
01776             String message,
01777             BotReply botReply,
01778             ArrayList<String> vstars,
01779             ArrayList<String> vbotstars,
01780             int step) {
01781 
01782         String reply = botReply.getReply();
01783 
01784         Vector<String> starsList = new Vector<String>(vstars);
01785         Vector<String> botstarsList = new Vector<String>(vbotstars);
01786 
01787         // Pad the stars.
01788         starsList.add(0, "");
01789         botstarsList.add(0, "");
01790 
01791         // Set a default first star.
01792         if (starsList.size() == 1) {
01793             starsList.add("undefined");
01794         }
01795         if (botstarsList.size() == 1) {
01796             botstarsList.add("undefined");
01797         }
01798 
01799         // Convert the stars into simple arrays.
01800         String[] stars    = Util.Sv2s(starsList);
01801         String[] botstars = Util.Sv2s(botstarsList);
01802 
01803         // Shortcut tags.
01804         reply = reply.replaceAll("<person>",    "{person}<star>{/person}");
01805         reply = reply.replaceAll("<@>",         "{@<star>}");
01806         reply = reply.replaceAll("<formal>",    "{formal}<star>{/formal}");
01807         reply = reply.replaceAll("<sentence>",  "{sentence}<star>{/sentence}");
01808         reply = reply.replaceAll("<uppercase>", "{uppercase}<star>{/uppercase}");
01809         reply = reply.replaceAll("<lowercase>", "{lowercase}<star>{/lowercase}");
01810 
01811         // Quick tags.
01812         reply = reply.replaceAll("\\{weight=\\d+\\}", ""); // Remove {weight}s
01813         reply = reply.replaceAll("<input>", "<input1>");
01814         reply = reply.replaceAll("<reply>", "<reply1>");
01815         reply = reply.replaceAll("<id>", user);
01816         reply = reply.replaceAll("\\\\s", " ");
01817         reply = reply.replaceAll("\\\\n", "\n");
01818         reply = reply.replaceAll("\\\\", "\\");
01819         reply = reply.replaceAll("\\#", "#");
01820 
01821         // Stars
01822         reply = reply.replaceAll("<star>", stars[1]);
01823         reply = reply.replaceAll("<botstar>", botstars[1]);
01824         for (int i = 1; i < stars.length; i++) {
01825             reply = reply.replaceAll("<star" + i + ">", stars[i]);
01826         }
01827         for (int i = 1; i < botstars.length; i++) {
01828             reply = reply.replaceAll("<botstar" + i + ">", botstars[i]);
01829         }
01830         reply = reply.replaceAll("<(star|botstar)\\d+>", "");
01831 
01832         // Input and reply tags.
01833         if (reply.indexOf("<input") > -1) {
01834             Pattern reInput = Pattern.compile("<input([0-9])>");
01835             Matcher mInput  = reInput.matcher(reply);
01836             while (mInput.find()) {
01837                 String tag   = mInput.group(0);
01838                 int    index = Integer.parseInt(mInput.group(1));
01839                 String text  = profile.getInput(index).toLowerCase().replaceAll("[^a-z0-9 ]+","");
01840                 reply        = reply.replace(tag, text);
01841             }
01842         }
01843         if (reply.indexOf("<reply") > -1) {
01844             Pattern reReply = Pattern.compile("<reply([0-9])>");
01845             Matcher mReply  = reReply.matcher(reply);
01846             while (mReply.find()) {
01847                 String tag   = mReply.group(0);
01848                 int    index = Integer.parseInt(mReply.group(1));
01849                 String text  = profile.getReply(index).toLowerCase().replaceAll("[^a-z0-9 ]+","");
01850                 reply        = reply.replace(tag, text);
01851             }
01852         }
01853 
01854         // {random} tag
01855         if (reply.indexOf("{random}") > -1) {
01856             Pattern reRandom = Pattern.compile("\\{random\\}(.+?)\\{\\/random\\}");
01857             Matcher mRandom  = reRandom.matcher(reply);
01858             while (mRandom.find()) {
01859                 String tag          = mRandom.group(0);
01860                 String[] candidates = mRandom.group(1).split("\\|");
01861                 String chosen = candidates [ rand.nextInt(candidates.length) ];
01862                 reply = reply.replace(tag, chosen);
01863             }
01864         }
01865 
01866         // <bot> tag
01867         if (reply.indexOf("<bot") > -1) {
01868             Pattern reBot = Pattern.compile("<bot (.+?)>");
01869             Matcher mBot  = reBot.matcher(reply);
01870             while (mBot.find()) {
01871                 String tag = mBot.group(0);
01872                 String var = mBot.group(1);
01873 
01874                 // Have this?
01875                 if (vars.containsKey(var)) {
01876                     reply = reply.replace(tag, vars.get(var));
01877                 }
01878                 else {
01879                     reply = reply.replace(tag, "undefined");
01880                 }
01881             }
01882         }
01883 
01884         // <env> tag
01885         if (reply.indexOf("<env") > -1) {
01886             Pattern reEnv = Pattern.compile("<env (.+?)>");
01887             Matcher mEnv  = reEnv.matcher(reply);
01888             while (mEnv.find()) {
01889                 String tag = mEnv.group(0);
01890                 String var = mEnv.group(1);
01891 
01892                 // Have this?
01893                 if (globals.containsKey(var)) {
01894                     reply = reply.replace(tag, globals.get(var));
01895                 }
01896                 else {
01897                     reply = reply.replace(tag, "undefined");
01898                 }
01899             }
01900         }
01901 
01902         // {!stream} tag
01903         if (reply.indexOf("{!") > -1) {
01904             Pattern reStream = Pattern.compile("\\{\\!(.+?)\\}");
01905             Matcher mStream  = reStream.matcher(reply);
01906             while (mStream.find()) {
01907                 String tag = mStream.group(0);
01908                 String code = mStream.group(1);
01909                 say("Stream new code in: " + code);
01910 
01911                 // Stream it.
01912                 this.stream(code);
01913                 reply = reply.replace(tag, "");
01914             }
01915         }
01916 
01917         // {person}
01918         if (reply.indexOf("{person}") > -1) {
01919             Pattern rePerson = Pattern.compile("\\{person\\}(.+?)\\{\\/person\\}");
01920             Matcher mPerson  = rePerson.matcher(reply);
01921             while (mPerson.find()) {
01922                 String tag  = mPerson.group(0);
01923                 String text = mPerson.group(1);
01924 
01925                 // Run person substitutions.
01926                 say("Run person substitutions: before: " + text);
01927                 text = Util.substitute(person_s, person, text);
01928                 say("After: " + text);
01929                 reply = reply.replace(tag, text);
01930             }
01931         }
01932 
01933         // {formal,uppercase,lowercase,sentence} tags
01934         if (reply.indexOf("{formal}") > -1 || reply.indexOf("{sentence}") > -1 ||
01935                 reply.indexOf("{uppercase}") > -1 || reply.indexOf("{lowercase}") > -1) {
01936             String[] tags = { "formal", "sentence", "uppercase", "lowercase" };
01937             for (int i = 0; i < tags.length; i++) {
01938                 Pattern reTag = Pattern.compile("\\{" + tags[i] + "\\}(.+?)\\{\\/" + tags[i] + "\\}");
01939                 Matcher mTag  = reTag.matcher(reply);
01940                 while (mTag.find()) {
01941                     String tag  = mTag.group(0);
01942                     String text = mTag.group(1);
01943 
01944                     // String transform.
01945                     text = stringTransform(tags[i], text);
01946                     reply = reply.replace(tag, text);
01947                 }
01948             }
01949         }
01950 
01951         // <set> tag
01952         if (reply.indexOf("<set") > -1) {
01953             Pattern reSet = Pattern.compile("<set (.+?)=(.+?)>");
01954             Matcher mSet  = reSet.matcher(reply);
01955             while (mSet.find()) {
01956                 String tag   = mSet.group(0);
01957                 String var   = mSet.group(1);
01958                 String value = mSet.group(2);
01959 
01960                 // Set the uservar.
01961                 profile.set(var, value);
01962                 reply = reply.replace(tag, "");
01963                 say("Set user var " + var + "=" + value);
01964             }
01965         }
01966 
01967         // <add, sub, mult, div> tags
01968         if (reply.indexOf("<add") > -1 || reply.indexOf("<sub") > -1 ||
01969                 reply.indexOf("<mult") > -1 || reply.indexOf("<div") > -1) {
01970             String[] tags = { "add", "sub", "mult", "div" };
01971             for (int i = 0; i < tags.length; i++) {
01972                 Pattern reTag = Pattern.compile("<" + tags[i] + " (.+?)=(.+?)>");
01973                 Matcher mTag  = reTag.matcher(reply);
01974                 while (mTag.find()) {
01975                     String tag   = mTag.group(0);
01976                     String var   = mTag.group(1);
01977                     String value = mTag.group(2);
01978 
01979                     // Get the user var.
01980                     String curvalue = profile.get(var);
01981                     int current = 0;
01982                     if (!curvalue.equals("undefined")) {
01983                         // Convert it to a int.
01984                         try {
01985                             current = Integer.parseInt(curvalue);
01986                         } catch (NumberFormatException e) {
01987                             // Current value isn't a number!
01988                             reply = reply.replace(tag, "[ERR: Can't \"" + tags[i] + "\" non-numeric variable " + var + "]");
01989                             continue;
01990                         }
01991                     }
01992 
01993                     // Value must be a number too.
01994                     int modifier = 0;
01995                     try {
01996                         modifier = Integer.parseInt(value);
01997                     } catch (NumberFormatException e) {
01998                         reply = reply.replace(tag, "[ERR: Can't \"" + tags[i] + "\" non-numeric value " + value + "]");
01999                         continue;
02000                     }
02001 
02002                     // Run the operation.
02003                     if (tags[i].equals("add")) {
02004                         current += modifier;
02005                     }
02006                     else if (tags[i].equals("sub")) {
02007                         current -= modifier;
02008                     }
02009                     else if (tags[i].equals("mult")) {
02010                         current *= modifier;
02011                     }
02012                     else {
02013                         // Don't divide by zero.
02014                         if (modifier == 0) {
02015                             reply = reply.replace(tag, "[ERR: Can't divide by zero!]");
02016                             continue;
02017                         }
02018                         current /= modifier;
02019                     }
02020 
02021                     // Store the new value.
02022                     profile.set(var, Integer.toString(current));
02023                     reply = reply.replace(tag, "");
02024                 }
02025             }
02026         }
02027 
02028         // <get> tag
02029         if (reply.indexOf("<get") > -1) {
02030             Pattern reGet = Pattern.compile("<get (.+?)>");
02031             Matcher mGet  = reGet.matcher(reply);
02032             while (mGet.find()) {
02033                 String tag = mGet.group(0);
02034                 String var = mGet.group(1);
02035 
02036                 // Get the user var.
02037                 reply = reply.replace(tag, profile.get(var));
02038             }
02039         }
02040 
02041         // {topic} tag
02042         if (reply.indexOf("{topic=") > -1) {
02043             Pattern reTopic = Pattern.compile("\\{topic=(.+?)\\}");
02044             Matcher mTopic  = reTopic.matcher(reply);
02045             while (mTopic.find()) {
02046                 String tag   = mTopic.group(0);
02047                 String topic = mTopic.group(1);
02048                 say("Set user's topic to: " + topic);
02049                 profile.set("topic", topic);
02050                 reply = reply.replace(tag, "");
02051             }
02052         }
02053 
02054         // {@redirect} tag
02055         if (reply.indexOf("{@") > -1) {
02056             Pattern reRed = Pattern.compile("\\{@(.+?)\\}");
02057             Matcher mRed  = reRed.matcher(reply);
02058             while (mRed.find()) {
02059                 String tag    = mRed.group(0);
02060                 String target = mRed.group(1).trim();
02061 
02062                 // Do the reply redirect.
02063                 botReply = this.reply(user, target, false, step+1);
02064                 reply = reply.replace(tag, botReply.getReply());
02065             }
02066         }
02067 
02068 
02069 
02070         // <call> tag
02071         if (reply.indexOf("<call>") > -1) {
02072             Pattern reCall = Pattern.compile("<call>(.+?)<\\/call>");
02073             Matcher mCall  = reCall.matcher(reply);
02074             while (mCall.find()) {
02075                 String tag = mCall.group(0);
02076                 String data = mCall.group(1);
02077                 String[] parts = data.split(" ");
02078                 String name = parts[0];
02079                 Vector<String> args = new Vector<String>();
02080                 for (int i = 1; i < parts.length; i++) {
02081                     args.add(parts[i]);
02082                 }
02083 
02084                 // See if we know of this object.
02085                 if (objects.containsKey(name)) {
02086                     // What language handles it?
02087                     String lang = objects.get(name);
02088                     botReply = handlers.get(lang).onCall(
02089                             name, user, Util.Sv2s(args));
02090                     if (botReply.getReply() != null) {
02091                         reply = reply.replace(tag, botReply.getReply());
02092                     } else {
02093                         reply = reply.replace(tag, "[ERR: Handler found but reply is null]");
02094                     }
02095                 }
02096                 else {
02097                     reply = reply.replace(tag, "[ERR: Object Not Found]");
02098                 }
02099             }
02100         }
02101 
02102         botReply.setReply(reply);
02103 
02104         return botReply;
02105     }
02106 
02113     private String stringTransform (String format, String text) {
02114         if (format.equals("uppercase")) {
02115             return text.toUpperCase();
02116         }
02117         else if (format.equals("lowercase")) {
02118             return text.toLowerCase();
02119         }
02120         else if (format.equals("formal")) {
02121             // Capitalize Each First Letter
02122             String[] words = text.split(" ");
02123             say("wc: " + words.length);
02124             for (int i = 0; i < words.length; i++) {
02125                 say("word: " + words[i]);
02126                 String[] letters = words[i].split("");
02127                 say("cc: " + letters.length);
02128                 if (letters.length > 1) {
02129                     say("letter 1: " + letters[1]);
02130                     letters[1] = letters[1].toUpperCase();
02131                     say("new letter 1: " + letters[1]);
02132                     words[i] = Util.join(letters, "");
02133                     say("new word: " + words[i]);
02134                 }
02135             }
02136             return Util.join(words, " ");
02137         }
02138         else if (format.equals("sentence")) {
02139             // Uppercase the first letter of the first word.
02140             String[] letters = text.split("");
02141             if (letters.length > 1) {
02142                 letters[1] = letters[1].toUpperCase();
02143             }
02144             return Util.join(letters, "");
02145         }
02146         else {
02147             return "[ERR: Unknown String Transform " + format + "]";
02148         }
02149     }
02150 
02157     private String formatMessage (String message) {
02158         // Lowercase it first.
02159         message = message.toLowerCase();
02160 
02161         // Run substitutions.
02162         message = Util.substitute(subs_s, subs, message);
02163 
02164         // Sanitize what's left.
02165         message = message.replaceAll("\\<>", "");
02166         message = Normalizer.normalize(message, Normalizer.Form.NFD);
02167         message = message.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
02168 
02169         return message;
02170     }
02171 
02172     /*-----------------------*/
02173     /*-- Developer Methods --*/
02174     /*-----------------------*/
02175 
02179     public void dumpSorted() {
02180         String[] topics = this.topics.listTopics();
02181         for (int t = 0; t < topics.length; t++) {
02182             String topic = topics[t];
02183             String[] triggers = this.topics.topic(topic).listTriggers();
02184 
02185             // Dump.
02186             System.out.println("Topic: " + topic);
02187             for (int i = 0; i < triggers.length; i++) {
02188                 System.out.println("       " + triggers[i]);
02189             }
02190         }
02191     }
02192 
02196     public void dumpTopics () {
02197         // Dump the topic list.
02198         System.out.println("{");
02199         String[] topicList = topics.listTopics();
02200         for (int t = 0; t < topicList.length; t++) {
02201             String topic = topicList[t];
02202             String extra = "";
02203 
02204             // Includes? Inherits?
02205             String[] includes = topics.topic(topic).includes();
02206             String[] inherits = topics.topic(topic).inherits();
02207             if (includes.length > 0) {
02208                 extra = "includes ";
02209                 for (int i = 0; i < includes.length; i++) {
02210                     extra += includes[i] + " ";
02211                 }
02212             }
02213             if (inherits.length > 0) {
02214                 extra += "inherits ";
02215                 for (int i = 0; i < inherits.length; i++) {
02216                     extra += inherits[i] + " ";
02217                 }
02218             }
02219             System.out.println("  '" + topic + "' " + extra + " => {");
02220 
02221             // Dump the trigger list.
02222             String[] trigList = topics.topic(topic).listTriggers();
02223             for (int i = 0; i < trigList.length; i++) {
02224                 String trig = trigList[i];
02225                 System.out.println("    '" + trig + "' => {");
02226 
02227                 // Dump the replies.
02228                 String[] reply = topics.topic(topic).trigger(trig).listReplies();
02229                 if (reply.length > 0) {
02230                     System.out.println("      'reply' => [");
02231                     for (int r = 0; r < reply.length; r++) {
02232                         System.out.println("        '" + reply[r] + "',");
02233                     }
02234                     System.out.println("      ],");
02235                 }
02236 
02237                 // Dump the conditions.
02238                 String[] cond = topics.topic(topic).trigger(trig).listConditions();
02239                 if (cond.length > 0) {
02240                     System.out.println("      'condition' => [");
02241                     for (int r = 0; r < cond.length; r++) {
02242                         System.out.println("        '" + cond[r] + "',");
02243                     }
02244                     System.out.println("      ],");
02245                 }
02246 
02247                 // Dump the redirects.
02248                 String[] red = topics.topic(topic).trigger(trig).listRedirects();
02249                 if (red.length > 0) {
02250                     System.out.println("      'redirect' => [");
02251                     for (int r = 0; r < red.length; r++) {
02252                         System.out.println("        '" + red[r] + "',");
02253                     }
02254                     System.out.println("      ],");
02255                 }
02256 
02257                 System.out.println("    },");
02258             }
02259 
02260             System.out.println("  },");
02261         }
02262     }
02263 
02264     /*-------------------*/
02265     /*-- Debug Methods --*/
02266     /*-------------------*/
02267 
02273     protected void say (String line) {
02274         if (this.debug) {
02275             System.out.println("[RS] " + line);
02276         }
02277     }
02278 
02284     private void cry (String line) {
02285         System.out.println("<RS> " + line);
02286     }
02287 
02295     private void cry (String text, String file, int line) {
02296         System.out.println("<RS> " + text + " at " + file + " line " + line + ".");
02297     }
02298 
02304     private void trace (IOException e) {
02305         if (this.debug) {
02306             e.printStackTrace();
02307         }
02308     }
02309 }


alfred_bot
Author(s): Mickael Gaillard , Erwan Le Huitouze
autogenerated on Tue Jun 14 2016 01:34:50