ai.susi.mind.SusiAction.java Source code

Java tutorial

Introduction

Here is the source code for ai.susi.mind.SusiAction.java

Source

/**
 *  SusiAction
 *  Copyright 29.06.2016 by Michael Peter Christen, @0rb1t3r
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *  
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program in the file lgpl21.txt
 *  If not, see <http://www.gnu.org/licenses/>.
 */

package ai.susi.mind;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;

import ai.susi.tools.TimeoutMatcher;

/**
 * An action is an application on the information deduced during inferences on mind states
 * as they are represented in an argument. If we want to produce respond sentences or if
 * we want to visualize deduces data as a graph or in a picture, thats an action.
 */
public class SusiAction {

    public static enum RenderType {
        answer, table, piechart, rss, self, websearch, anchor, map;
    }

    public static enum SelectionType {
        random, roundrobin;
    }

    public static enum DialogType {
        answer, // a sentence which may end a conversation
        question, // a sentence which may cause that the user answers with a fact
        reply; // a response of an answers of the user from a question aked by sudy
        public int getSubscore() {
            return this.ordinal();
        }
    }

    private final static Random random = new Random(System.currentTimeMillis());

    private JSONObject json;

    /**
     * initialize an action using a json description.
     * @param json
     */
    public SusiAction(JSONObject json) {
        this.json = json;
    }

    public static JSONObject answerAction(String[] answers) {
        JSONObject json = new JSONObject();
        JSONArray phrases = new JSONArray();
        json.put("type", RenderType.answer.name());
        json.put("select", SelectionType.random.name());
        json.put("phrases", phrases);
        for (String answer : answers)
            phrases.put(answer.trim());
        return json;
    }

    /**
     * Get the render type. That can be used to filter specific information from the action JSON object
     * to create specific activities like 'saying' a sentence, painting a graph and so on.
     * @return the action type
     */
    public RenderType getRenderType() {
        if (renderTypeCache == null)
            renderTypeCache = this.json.has("type") ? RenderType.valueOf(this.json.getString("type")) : null;
        return renderTypeCache;
    }

    private RenderType renderTypeCache = null;

    public DialogType getDialogType() {
        if (this.getRenderType() != RenderType.answer)
            return DialogType.answer;
        return getDialogType(getPhrases());
    }

    public static DialogType getDialogType(Collection<String> phrases) {
        DialogType type = DialogType.reply;
        for (String phrase : phrases) {
            DialogType t = getDialogType(phrase);
            if (t.getSubscore() < type.getSubscore())
                type = t;
        }
        return type;
    }

    public static DialogType getDialogType(String phrase) {
        if (phrase.indexOf('?') > 3) { // the question mark must not be at the beginning
            return phrase.indexOf(". ") >= 0 ? DialogType.reply : DialogType.question;
        }
        return DialogType.answer;
    }

    /**
     * If the action involves the reproduction of phrases (=strings) then they can be retrieved here
     * @return the action phrases
     */
    public ArrayList<String> getPhrases() {
        if (phrasesCache == null) {
            ArrayList<String> a = new ArrayList<>();
            // actions may have either a single expression "expression" or a phrases object with 
            if (this.json.has("expression")) {
                a.add(this.json.getString("expression"));
            } else if (this.json.has("phrases")) {
                this.json.getJSONArray("phrases").forEach(p -> a.add((String) p));
            } else
                return null;
            phrasesCache = a;
        }
        return phrasesCache;
    }

    private ArrayList<String> phrasesCache = null;

    /**
     * if the action contains more String attributes where these strings are named, they can be retrieved here
     * @param attr the name of the string attribute
     * @return the action string
     */
    public String getStringAttr(String attr) {
        return this.json.has(attr) ? this.json.getString(attr) : "";
    }

    /**
     * If the action contains integer attributes, they can be retrieved here
     * @param attr the name of the integer attribute
     * @return the integer number
     */
    public int getIntAttr(String attr) {
        return this.json.has(attr) ? this.json.getInt(attr) : 0;
    }

    final static Pattern visible_assignment = Pattern
            .compile("(?:(?:.*)[\\?\\!\\h,\\.;-])+([^\\^]+?)>([_a-zA-Z0-9]+)(?:[\\?\\!\\h,\\.;-](?:.*))?+");
    final static Pattern blind_assignment = Pattern
            .compile("(?:.*)\\^(.*?)\\^>([_a-zA-Z0-9]+)(?:[\\?\\!\\h,\\.;-](?:.*))?+");
    final static Pattern self_referrer = Pattern.compile(".*`([^`]*?)`.*");

    /**
     * Action descriptions are templates for data content. Strings may refer to arguments from
     * a thought deduction using variable templates. I.e. "$name$" inside an action string would
     * refer to an data entity in an thought argument which has the name "name". Applying the
     * Action to a thought will instantiate such variable templates and produces a new String
     * attribute named "expression"
     * @param thoughts an argument from previously applied inferences
     * @return the action with the attribute "expression" instantiated by unification of the thought with the action
     */
    public SusiAction execution(SusiArgument thoughts, SusiMind mind, String client) {
        if ((this.getRenderType() == RenderType.answer || this.getRenderType() == RenderType.self)
                && this.json.has("phrases")) {
            // transform the answer according to the data
            ArrayList<String> a = getPhrases();
            String phrase = a.get(random.nextInt(a.size()));
            String expression = thoughts.unify(phrase);
            if (expression != null) {
                // transform the answer according to the data
                // this is the final chance that we can add another thought according to a memorizing skill in the answer string
                Matcher m;

                // self-referrer evaluate contents from the answers expressions as recursion: susi is asked again
                while (new TimeoutMatcher(m = self_referrer.matcher(expression)).matches()) {
                    String observation = m.group(1);
                    expression = expression.substring(0, m.start(1) - 1)
                            + mind.react(observation, client, new SusiThought())
                            + expression.substring(m.end(1) + 1);
                }

                // assignments set variables from the result expressions. These can be visible or invisible
                while (new TimeoutMatcher(m = visible_assignment.matcher(expression)).matches()) {
                    String observation = m.group(1);
                    String variable = m.group(2);
                    expression = expression.substring(0, m.end(1)) + expression.substring(m.end(2));
                    // write the variable v as side-effect into the thoughts argument
                    thoughts.think(new SusiThought().addObservation(variable, observation));
                }
                while (new TimeoutMatcher(m = blind_assignment.matcher(expression)).matches()) {
                    String observation = m.group(1);
                    String variable = m.group(2);
                    expression = expression.substring(0, m.start(1) - 1) + expression.substring(m.end(2));
                    // write the variable v as side-effect into the thoughts argument
                    thoughts.think(new SusiThought().addObservation(variable, observation));
                }

                // find an response type: self-recursion or answer
                if (this.getRenderType() == RenderType.answer) {
                    // the expression is answered to the communication partner
                    this.json.put("expression", expression);
                }
                if (this.getRenderType() == RenderType.self) {
                    // recursive call susi with the answer
                    expression = mind.react(expression, client, new SusiThought());
                    this.json.put("expression", expression);
                    this.phrasesCache = null; // important, otherwise the expression is not recognized
                    // patch the render type
                    this.json.put("type", RenderType.answer.name());
                    this.renderTypeCache = RenderType.answer;
                }
            }
        }
        if (this.getRenderType() == RenderType.websearch && this.json.has("query")) {
            this.json.put("query", thoughts.unify(getStringAttr("query")));
        }
        if (this.getRenderType() == RenderType.anchor && this.json.has("link") && this.json.has("text")) {
            this.json.put("link", thoughts.unify(getStringAttr("link")));
            this.json.put("text", thoughts.unify(getStringAttr("text")));
        }
        if (this.getRenderType() == RenderType.map && this.json.has("latitude") && this.json.has("longitude")
                && this.json.has("zoom")) {
            this.json.put("latitude", thoughts.unify(getStringAttr("latitude")));
            this.json.put("longitude", thoughts.unify(getStringAttr("longitude")));
            this.json.put("zoom", thoughts.unify(getStringAttr("zoom")));
        }
        return this;
    }

    /**
     * An action is backed with a JSON data structure. That can be retrieved here.
     * @return the json structure of the action
     */
    public JSONObject toJSONClone() {
        JSONObject j = new JSONObject(true);
        this.json.keySet().forEach(key -> j.put(key, this.json.get(key))); // make a clone
        if (j.has("expression")) {
            j.remove("phrases");
            j.remove("select");
        }
        return j;
    }

    /**
     * toString
     * @return return the json representation of the object as a string
     */
    public String toString() {
        return toJSONClone().toString();
    }
}