package io.github.agentsoz.jill.core.beliefbase.abs;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.core.beliefbase.Belief;
import io.github.agentsoz.jill.core.beliefbase.BeliefBase;
import io.github.agentsoz.jill.core.beliefbase.BeliefBaseException;
import io.github.agentsoz.jill.core.beliefbase.BeliefSet;
import io.github.agentsoz.jill.core.beliefbase.BeliefSetField;
import io.github.agentsoz.jill.util.Log;
import io.github.agentsoz.jill.util.SparseBitSet;

import java.io.Console;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
ABeliefStore class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
@SuppressWarnings("PMD.GodClass")
public class ABeliefStore extends BeliefBase {

  private static final float loadfactor = 0.9f;

  public enum Operator {
    EQ, NE, LT, GT,
  }

  private static ConcurrentHashMap<String, BeliefSet> beliefsets; // NOPMD - canot be final
  private static ConcurrentHashMap<Integer, BeliefSet> beliefsetsByID; // NOPMD - canot be final
  private static ConcurrentHashMap<Belief, Integer> beliefs; // NOPMD - canot be final
  private static ConcurrentHashMap<Integer, Belief> beliefsByID; // NOPMD - canot be final
  private static ConcurrentHashMap<String, AQuery> queries; // NOPMD - canot be final
  private static ConcurrentHashMap<String, Set<Belief>> cachedresults; // NOPMD - canot be final
  private static SparseBitSet[] agents2beliefs; // NOPMD - canot be final

  /**
   * Constructs a new belief store.
   *
   * @param nagents the store will manage beliefs for thsi number of agents
   * @param nthreads the number of threads that may concurrently query/update this belief set
   */
  public ABeliefStore(int nagents, int nthreads) {
    beliefsets = new ConcurrentHashMap<String, BeliefSet>(1, loadfactor, nthreads);
    beliefsetsByID =
        new ConcurrentHashMap<Integer, BeliefSet>(beliefsets.size(), loadfactor, nthreads);
    beliefs = new ConcurrentHashMap<Belief, Integer>(nagents * 10, loadfactor, nthreads);
    beliefsByID = new ConcurrentHashMap<Integer, Belief>(beliefs.size(), loadfactor, nthreads);
    queries = new ConcurrentHashMap<String, AQuery>(64, loadfactor, nthreads);
    cachedresults =
        new ConcurrentHashMap<String, Set<Belief>>(queries.size(), loadfactor, nthreads);
    agents2beliefs = new SparseBitSet[nagents];
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public boolean createBeliefSet(int agentid, String name, BeliefSetField[] fields)
      throws BeliefBaseException {
    if (beliefsets.containsKey(name)) {
      return false;
    }
    // Add the beliefset to the list of beliefsets
    BeliefSet bs = new BeliefSet(beliefsets.size(), name, fields);
    beliefsets.put(name, bs);
    beliefsetsByID.put(bs.getId(), bs);
    return true;
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public boolean addBelief(int agentid, String beliefsetName, Object... tuple)
      throws BeliefBaseException {
    // Check that the beliefset exists
    if (!beliefsets.containsKey(beliefsetName)) {
      throw new BeliefBaseException("Belief set '" + beliefsetName + "' does not exist");
    }
    // Create a new Belief
    Belief belief = new Belief(beliefs.size(), beliefsets.get(beliefsetName).getId(), tuple);
    // Add it to the list of beliefs
    int id;
    if (!beliefs.containsKey(belief)) {
      id = beliefs.size();
      beliefs.put(belief, id);
      beliefsByID.put(belief.getId(), belief);

    } else {
      id = beliefs.get(belief);
    }
    // Add it to the agents beliefs
    SparseBitSet bits = agents2beliefs[agentid];
    if (bits == null) {
      bits = new SparseBitSet();
    }
    bits.set(id);
    agents2beliefs[agentid] = bits;
    // Update the cached results
    for (String query : cachedresults.keySet()) {
      Set<Belief> results = cachedresults.get(query);
      if (match(belief, queries.get(query))) {
        results.add(belief);
      }
    }
    return true;
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public boolean eval(int agentid, String key) throws BeliefBaseException {
    return !query(agentid, key).isEmpty();
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public Set<Belief> query(int agentid, String key) throws BeliefBaseException {
    // Get the cached query if we have seen it before, else parse it
    AQuery query = null;
    if (queries.containsKey(key)) {
      query = queries.get(key);
    } else {
      // Valid queries have the following syntax
      // beliefset.field OP value
      // where OP is one of =, !=, <, or >
      if (key == null) {
        throw new BeliefBaseException(logsuffix(agentid) + "'null' query");
      }
      Pattern pattern = Pattern.compile("(\\w+)\\.(\\w+)\\s*([=<>(!=)])\\s*(.+)");
      Matcher matcher = pattern.matcher(key);
      if (!matcher.matches()) {
        throw new BeliefBaseException(logsuffix(agentid) + "invalid query '" + key
            + "' : syntax not of the form beliefset.field <op> value");
      }
      String strBeliefset = matcher.group(1);
      String strField = matcher.group(2);
      String strOp = matcher.group(3);
      String strVal = matcher.group(4);
      try {
        query = parseQuery(agentid, strBeliefset, strField, strOp, strVal);
        queries.put(key, query);
      } catch (BeliefBaseException e) {
        throw new BeliefBaseException(logsuffix(agentid) + "could not parse query: " + key, e);
      }
    }
    // Get the cached results if they exist,
    // else perform the query and cache the results
    Set<Belief> results = null;
    if (cachedresults.containsKey(key)) {
      results = cachedresults.get(key);
    } else {
      results = performQuery(query, beliefs);
      cachedresults.put(key, results);
    }

    // Finally, filter the results for this agent
    Set<Belief> matches = filterResultsForAgent(agentid, results);
    Log.debug("Agent " + agentid + " found " + matches.size() + " matches for the query");

    return matches;
  }

  private AQuery parseQuery(int agentid, String strBeliefset, String strField, String strOp,
      String strVal) throws BeliefBaseException {
    if (!beliefsets.containsKey(strBeliefset)) {
      throw new BeliefBaseException(
          logsuffix(agentid) + "belief set '" + strBeliefset + "' does not exist");
    }
    BeliefSet beliefset = beliefsets.get(strBeliefset);
    BeliefSetField field = beliefset.getFieldByName(strField);
    int fieldIndex = beliefset.getIndex(field);
    if (field == null || fieldIndex == -1) {
      throw new BeliefBaseException(logsuffix(agentid) + "could not retrieve belief set field '"
          + strField + "' from belief set " + strBeliefset);
    }
    Object val = null;
    try {
      val = string2type(field.getType(), strVal);
    } catch (BeliefBaseException e) {
      throw new BeliefBaseException(logsuffix(agentid) + e.getMessage(), e);
    }
    int id = beliefset.getId();
    Operator op = "=".equals(strOp) ? Operator.EQ
        : "<".equals(strOp) ? Operator.LT : ">".equals(strOp) ? Operator.GT : Operator.NE;
    return new AQuery(id, fieldIndex, op, val);
  }

  private static Set<Belief> performQuery(AQuery query,
      ConcurrentHashMap<Belief, Integer> beliefs2) {
    assert (query != null);
    assert (beliefs2 != null);
    Set<Belief> results = Collections.newSetFromMap(new ConcurrentHashMap<Belief, Boolean>());
    for (Belief belief : beliefs2.keySet()) {
      if (match(belief, query)) {
        results.add(belief);
      }
    }
    return results;
  }

  private static Set<Belief> filterResultsForAgent(int agentid, Set<Belief> results) {
    assert (results != null);
    // Finally, check if this result holds true for this agent
    HashSet<Belief> matches = new HashSet<Belief>();
    SparseBitSet agentbeliefs = agents2beliefs[agentid];
    if (agentbeliefs == null) {
      return matches;
    }
    /*
     * for (Belief belief : results) { int beliefID = beliefs2.get(belief); // check if the agent
     * has this belief if (agentbeliefs.get(beliefID)) { matches.add(belief); } }
     */
    for (int i = agentbeliefs.nextSetBit(0); i >= 0; i = agentbeliefs.nextSetBit(i + 1)) {
      // check if this belief exists in the results set
      Belief belief = beliefsByID.get(i);
      if (results.contains(belief)) {
        matches.add(belief);
      }
    }
    return matches;
  }

  private Object string2type(Class<?> type, String str) throws BeliefBaseException {
    Object val = null;
    String stype = type.getName();
    try {
      switch (stype) {
        case "java.lang.String":
          val = str;
          break;
        case "java.lang.Integer":
          val = Integer.valueOf(str);
          break;
        case "java.lang.Double":
          val = Double.valueOf(str);
          break;
        case "java.lang.Boolean":
          val = Boolean.valueOf(str);
          break;
        default:
          // FIXME: Ignoring other types for now (while we are still developing new types)
          break;
      }
    } catch (NumberFormatException e) {
      throw new BeliefBaseException("value '" + str + "' is not of type " + stype, e);
    }
    return val;
  }

  /**
   * Gets the type of the given object.
   *
   * @param obj the object whose type is being queried
   * @return the type of the object, or null if unknown
   */
  public static String getType(Object obj) {
    if (obj == null) {
      return null;
    }
    String type = null;
    if (obj instanceof String || obj instanceof Integer || obj instanceof Double
        || obj instanceof Boolean) {
      type = obj.getClass().getName();
    }
    return type;
  }

  /**
   * Checks if the given query run on the given belief returns a match.
   *
   * @param belief the belief being queried
   * @param query the query being performed on the belief
   * @return true if there is a match, false otherwise
   */
  private static boolean match(Belief belief, AQuery query) {
    assert (belief != null);
    assert (query != null);
    if (belief.getBeliefset() != query.getBeliefset()) {
      return false;
    }
    switch (query.getOp()) {
      case EQ:
        Object lhs = belief.getTuple()[query.getField()];
        Object rhs = query.getValue();
        // Match wildcard or exact string
        return "*".equals(rhs) || lhs.equals(rhs);
      case GT:
        // TODO: Handle Operator.GT
      case LT:
        // TODO: Handle Operator.LT
      default:
        break;
    }
    return false;
  }

  /**
   * Convinience function to return a logging prefix for this agent.
   *
   * @param agentid the ID of this agnet
   * @return the prefix to use for loggin purposes
   */
  private String logsuffix(int agentid) {
    return getClass().getSimpleName() + ": agent " + agentid + ": ";
  }

  /**
   * Sample program to test pattern matching.
   *
   * @param args command line arguments
   * @throws BeliefBaseException thrown if something went wrong
   */
  public static void main(String[] args) throws BeliefBaseException {
    BeliefBase bb = new ABeliefStore(100, 4);
    bb.eval(0, "neighbour.age < 31");

    Console console = System.console();
    if (console == null) {
      System.err.println("No console.");
      System.exit(1);
    }
    while (true) {

      Pattern pattern = Pattern.compile(console.readLine("%nEnter your regex: "));

      Matcher matcher = pattern.matcher(console.readLine("Enter input string to search: "));

      boolean found = false;
      while (matcher.find()) {
        console.format(
            "I found the text" + " \"%s\" starting at " + "index %d and ending at index %d.%n",
            matcher.group(), matcher.start(), matcher.end());
        found = true;
      }
      if (!found) {
        console.format("No match found.%n");
      }
    }
  }

  /**
   * Gets the belief set field name (see
   * {@link io.github.agentsoz.jill.core.beliefbase.BeliefSetField}) for the given belief set
   * for the given agent.
   *
   * @param agentid the agent in question
   * @param beliefset the beliefset of that agent
   * @param index the index of the belief set field in that belief set
   * @return the name of the belief set field
   * @throws BeliefBaseException if something went wrong
   */
  public static String getFieldName(int agentid, int beliefset, int index)
      throws BeliefBaseException {
    if (beliefset < 0 || beliefset > beliefsets.size()) {
      throw new BeliefBaseException("belief set id " + beliefset + " is invalid");
    }
    BeliefSetField[] bsf = beliefsetsByID.get(beliefset).getFields();
    if (index < 0 || index >= bsf.length) {
      throw new BeliefBaseException("belief set field id " + index + " is invalid");
    }
    return bsf[index].getName();
  }

}


package io.github.agentsoz.jill.util;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import java.io.UnsupportedEncodingException;

/**
 * AString provides utility functions to convert a String object to a byte array for compressed
 * in-memory storage. Storing String objects as a byte array gives very significant memory savings,
 * since String objects have a large memory overhead (an empty String object still takes 128 bytes,
 * but takes no space in byte array format). This comes at the cost of CPU cycles required to
 * convert back to a String object before use, however this processing overhead is very low.
 *
 * @author dsingh
 * @version $Id: $Id
 */
public final class AString {

  /** The java.nio.charset to use for converting to/from byte array */
  private static final String CHARSET = "UTF-8";

  /**
   * This class cannot be instantiated.
   */
  private AString() {

  }

  /**
   * Encodes the String str into a sequence of bytes using the character set specified in CHARSET,
   * storing the result into a new byte array.
   *
   * @param str the String to convert from
   * @return the new byte array, or null if conversion was unsuccessful
   * @see String#getBytes(String)
   */
  public static byte[] toBytes(String str) {
    if (str == null) {
      return new byte[0];
    }
    byte[] val = null;
    try {
      val = str.getBytes(CHARSET);
    } catch (UnsupportedEncodingException e) { // NOPMD - ignore empty catch
      // Can never occur since CHARSET is correct and final
    }
    return val;
  }

  /**
   * Decodes the byte array bytes into a String using the character set specified in CHARSET,
   * storing the result into a new String.
   *
   * @param bytes the byte array to decode
   * @return the new decoded String, or null if conversion was unsuccessful
   */
  public static String toString(byte[] bytes) {
    if (bytes == null) {
      return null;
    }
    String str = null;
    try {
      str = new String(bytes, CHARSET);
    } catch (UnsupportedEncodingException e) { // NOPMD - ignore empty catch
      // Can never occur since CHARSET is correct and final
    }
    return str;
  }

}


package io.github.agentsoz.jill.lang;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.Main;
import io.github.agentsoz.jill.core.GlobalState;
import io.github.agentsoz.jill.core.beliefbase.Belief;
import io.github.agentsoz.jill.core.beliefbase.BeliefBase;
import io.github.agentsoz.jill.core.beliefbase.BeliefBaseException;
import io.github.agentsoz.jill.core.beliefbase.BeliefSetField;
import io.github.agentsoz.jill.struct.AObject;
import io.github.agentsoz.jill.util.AObjectCatalog;
import io.github.agentsoz.jill.util.Log;
import io.github.agentsoz.jill.util.Stack255;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintStream;
import java.util.HashSet;
import java.util.Set;

/**
 * Base class for all BDI-like agents in the system.
 *
 * @author dsingh
 * @version $Id: $Id
 */
public class Agent extends AObject {

  private final Logger logger = LoggerFactory.getLogger(Main.LOGGER_NAME);

  // FIXME: some Agent method are just by Jill only and should be final

  /**
   * References to this agent's top level goals.
   *
   *
For space efficiency, instead of storing references to {@link Goal} objects, we store
   * catalog IDs for goals in the {@link io.github.agentsoz.jill.core.GlobalState#goalTypes}
   * catalog. Since these references are of size byte, a maximum of 255 goal types are supported in
   * the system (globally, not per agent type). These 1-byte references give very significant
   * savings in space over Java object references, which on a 64-bit machine take 8-bytes each.
   *
   *
NOTE: If a limit of 255 goal types in the system turns out to be insufficient, then an agent
   * type reference could be added here (at the cost of another byte) and that would then allow 255
   * goal types per agent type.
   *
   */
  private byte[] goals; // This agent's goal-plan tree

  /**
   * This agent's intention stacks.
   *
   *
NOTE: This stack is of type {@link io.github.agentsoz.jill.util.Stack255} which supports a
   * maximum of 255 objects. This means that the active goal-plan execution trace cannot be longer
   * that 255. This should be more than enough for even the most complex goal-plan trees, but can be
   * an issue for recursive behaviours where a plan posts an instance of the same goal type that it
   * handles.
   */
  private final IntentionStacks stacks;

  /**
   * A handle to any registered meta-planning function.
   */
  private MetaPlan metaplan;

  private static BeliefBase beliefbase; // NOPMD - must not make this final
  private static AObjectCatalog agents; // NOPMD - must not make this final
  private Set<Belief> lastresult;

  /**
   * Creates a new agent with the given name.
   *
   * @param name the name of this agent; consider using concise names when dealing with very large
   *        numbers of agents
   */
  public Agent(String name) {
    super(name);
    beliefbase = GlobalState.beliefbase;
    agents = GlobalState.agents;
    lastresult = null;
    metaplan = null;
    // initialise the intention stacks
    stacks = new IntentionStacks();

  }

  /**
   * Gets the active intention stack of this agent.
   *
   * @return this agent's execution stack
   */
  public Stack255 getExecutionStack() {
    return stacks.getActiveStack();
  }

  /**
   * Selects and returns the next active intention stack of this agent.
   * @return the intention stack that was activated
   */
  public Stack255 nextActiveStack() {
    return stacks.nextActiveStack();
  }

  /**
   * Removes any empty intention stacks and resets the active stack to the first one.
   * @return the size of the intentions stacks after cleanup
   */
  public int cleanupStacks() {
    return stacks.cleanup();
  }

  /**
   * Creates and returns a new intention stack for this agent.
   *
   * @return the new execution stack
   */
  private Stack255 getNewExecutionStack() {
    return stacks.getEmptyIntentionStack();
  }

  /**
   * Posts the given goal. This will trigger the BDI execution engine to generate applicable plan
   * instances to handle this goal. One instance, from the available options, will be selected and
   * executed.
   *
   * @param goal the goal that this agent should try to achieve
   */
  public void post(Goal goal) {
    post(getNewExecutionStack(), goal);
  }

  /**
   * Push the given goal to the given stack.
   * @param stack the stack to push to new goal to
   * @param goal the goal to push
   */
  private void post(Stack255 stack, Goal goal) {
    synchronized (stack) {
      logger.debug("{} posting goal {}", Log.logPrefix(getId()), goal.getClass().getSimpleName());
      stack.push(goal);
      Main.setAgentIdle(getId(), false);
    }
    Main.flagMessageTo(Main.poolid(getId()));
  }

  /**
   * Posts the given goal as a subgoal in the current execution stack.
   * This will trigger the BDI execution engine to generate applicable plan
   * instances to handle this goal. One instance, from the available options, will be selected and
   * executed.
   *
   * @param goal the goal that this agent should try to achieve
   */
  public void subgoal(Goal goal) {
    post(getExecutionStack(), goal);
  }

  /**
   * Send a message to an agent.
   *
   * @param id the agent to send the message to
   * @param msg the message to send
   * @return true if the message was sent successfully, false otherwise
   */
  public boolean send(int id, Goal msg) {
    AObject obj = agents.get(id);
    if (obj == null) {
      logger.warn("{} attempted to send a message to unknown agent id '{}'", Log.logPrefix(getId()),
          id);
      return false;
    }
    logger.debug("{} is sending message of type {} to agent {}", Log.logPrefix(getId()),
        msg.getClass().getSimpleName(), id);
    ((Agent) obj).post(msg);
    return true;
  }

  /**
   * Send a message to this agent.
   *
   * @param name the agent to send the message to
   * @param msg the message to send
   * @return true if the message was sent successfully, false otherwise
   */
  public boolean send(String name, Goal msg) {
    AObject obj = agents.find(name);
    if (obj == null) {
      logger.warn("{} attempted to send a message to unknown agent '{}'", Log.logPrefix(getId()),
          name);
      return false;
    }
    ((Agent) obj).post(msg);
    return true;
  }

  /**
   *
start.

   *
   * @param writer a {@link java.io.PrintStream} object.
   * @param params an array of {@link java.lang.String} objects.
   */
  public void start(PrintStream writer, String[] params) {
    logger.debug("{} is starting", Log.logPrefix(getId()));
  }

  /**
   *
finish.

   */
  public void finish() {
    logger.debug("{} is finishing", Log.logPrefix(getId()));
  }

  /**
   * Returns this agent's top level goals.
   *
   * @return {@link io.github.agentsoz.jill.lang.Agent#goals}
   */
  public byte[] getGoals() {
    byte[] arr = new byte[goals.length];
    System.arraycopy(goals, 0, arr, 0, arr.length);
    return arr;
  }

  /**
   * Set's this agent's top level goals, i.e., {@link #goals}.
   *
   * @param bs an array of goal IDs (must exist in the
   *        {@link io.github.agentsoz.jill.core.GlobalState#goalTypes} catalog.
   */
  public void setGoals(byte[] bs) {
    goals = new byte[bs.length];
    System.arraycopy(bs, 0, goals, 0, goals.length);
  }

  /**
   * Creates a new belief set with the given fields.
   *
   *
Example usage:
   *
   *

   * BeliefSetField[] fields = {new BeliefSetField("name", String.class, true),
   *     new BeliefSetField("gender", String.class, false),};
   * createBeliefSet("neighbour", fields);
   *

   *
   * @param name a name for this new belief set
   * @param fields an array of belief set fields (see
   *        {@link io.github.agentsoz.jill.core.beliefbase.BeliefSetField})
   * @throws io.github.agentsoz.jill.core.beliefbase.BeliefBaseException exception
   *        (see {@link io.github.agentsoz.jill.core.beliefbase.BeliefSetField})
   */
  public void createBeliefSet(String name, BeliefSetField[] fields) throws BeliefBaseException {
    beliefbase.createBeliefSet(getId(), name, fields);
  }

  /**
   * Adds a new belief to the specified belief set.
   *
   * @param beliefsetName the belief set to add the belief to; must have been created previously
   *        using {@link #createBeliefSet(String, BeliefSetField[])}
   * @param tuple parameter list of field values; types must match the specification in
   *        {@link #createBeliefSet(String, BeliefSetField[])}
   * @throws io.github.agentsoz.jill.core.beliefbase.BeliefBaseException exception
   *        (see {@link io.github.agentsoz.jill.core.beliefbase.BeliefBaseException})
   */
  public void addBelief(String beliefsetName, Object... tuple) throws BeliefBaseException {
    beliefbase.addBelief(getId(), beliefsetName, tuple);
  }

  /**
   * Evaluates the given query against this agent's belief base.
   *
   * @param query the query to evaluate
   * @return true if the query returned results, false otherwise
   * @throws BeliefBaseException if something went wrong
   */
  public boolean eval(String query) throws BeliefBaseException {
    boolean result = beliefbase.eval(getId(), query);
    lastresult = (result) ? beliefbase.query(getId(), query) : new HashSet<Belief>();
    return result;
  }

  /**
   * Gets the results of the last query run (see {@link #eval(String)}).
   *
   * @return the set of beliefs that matches the last query
   */
  public Set<Belief> getLastResults() {
    return lastresult;
  }

  /**
   * Clears the results of the last query run (see {@link #eval(String)}).
   */
  public void clearLastResults() {
    if (lastresult != null) {
      lastresult.clear();
    }
    lastresult = null;
  }

  /**
   * Forces this agent to enter an idle state irrespective of whether it has any active intentions
   * or not. The agent will continue to remain in the suspected state until some event forces it to
   * become active again, at which point it will resume operation.
   *
   * @param val the new idle state of this agent
   */
  public void suspend(boolean val) {
    Main.setAgentIdle(getId(), val);
    Main.flagMessageTo(Main.poolid(getId()));
  }

  /**
   * Registers a meta-planning function for this agent. The registered function will be called by
   * the Jill engine with the available plan bindings, prior to any plan selection, giving the agent
   * the opportunity to do meta-level reasoning.
   *
   * @param metaplan the metaplan instance to register
   */
  public void registerMetaPlan(MetaPlan metaplan) {
    this.metaplan = metaplan;
  }

  /**
   * Called by the Jill engine with available plan bindings, to Allow this agent to perform
   * meta-level reasoning.
   *
   * @param bindings the available plan bindings
   */
  public final void notifyAgentPrePlanSelection(PlanBindings bindings) {
    if (metaplan == null) {
      return;
    }
    metaplan.consider(bindings);
  }

}


package io.github.agentsoz.jill.example.greeter;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.core.beliefbase.BeliefBaseException;
import io.github.agentsoz.jill.lang.Agent;
import io.github.agentsoz.jill.lang.Goal;
import io.github.agentsoz.jill.lang.Plan;
import io.github.agentsoz.jill.lang.PlanStep;
import io.github.agentsoz.jill.util.Log;

import java.util.Map;

/**
 *
GreetNeighbour class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
public class GreetNeighbour extends Plan {

  String neighbour;

  /**
   * Creates a new plan to greet a neighbour.
   *
   * @param agent the agent to which this plan belongs
   * @param goal the goal for which this is a plan
   * @param name NOT USED
   */
  public GreetNeighbour(Agent agent, Goal goal, String name) {
    super(agent, goal, name);
    body = steps;
    neighbour = "Unknown";
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public boolean context() {
    try {
      return getAgent().eval("neighbour.gender = male");
    } catch (BeliefBaseException e) {
      Log.error(e.getMessage());
    }
    return false;
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public void setPlanVariables(Map<String, Object> vars) {
    for (String attribute : vars.keySet()) {
      if ("name".equals(attribute)) {
        neighbour = (String) (vars.get(attribute));
      }
    }
  }

  PlanStep[] steps = {new PlanStep() {
    public void step() {
      Log.debug(getAgent().getName() + " says hello " + neighbour);
    }
  },};

}


package io.github.agentsoz.jill.core.beliefbase;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import com.google.gson.Gson;

import io.github.agentsoz.jill.struct.AObject;

import java.util.Arrays;

/**
 *
BeliefSet class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
public class BeliefSet extends AObject {

  private final BeliefSetField[] fields;

  /**
   * Constructs a new belief set.
   *
   * @param id the unique ID of this belief set (must follow sequence 0,1,2,3,...)
   * @param name a name for this belief set
   * @param fields the fields (columns) of this belief set
   */
  public BeliefSet(int id, String name, BeliefSetField[] fields) {
    super(name);
    setId(id);
    this.fields = fields;
  }

  /**
   * Gets the fields of this belief set.
   *
   * @return the fields
   */
  public BeliefSetField[] getFields() {
    return fields;
  }

  /**
   * Gets the field of this belief set that has the given name.
   *
   * @param name the name of the field to retrieve
   * @return the belief set field matching that name, or null if not found
   * @since 0.3.4
   */
  public BeliefSetField getFieldByName(String name) {
    BeliefSetField field = null;
    for (int i = 0; i < fields.length; i++) {
      if (name.equals(fields[i].getName())) {
        field = fields[i];
        break;
      }
    }
    return field;
  }

  /**
   * Gets the index (column) of the given field in this belief set.
   *
   * @param field the field for which the index is required
   * @return the index of that field, or -1 if not found
   * @since 0.3.4
   */
  public int getIndex(BeliefSetField field) {
    int index = -1;
    for (int i = 0; i < fields.length; i++) {
      if (field.equals(fields[i])) {
        index = i;
      }
    }
    return index;
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + Arrays.hashCode(fields);
    String name = getName();
    result = prime * result + ((name == null) ? 1 : name.hashCode());
    result = prime * result + Integer.hashCode(getId());
    return result;
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (!(obj instanceof BeliefSet)) {
      return false;
    }
    BeliefSet other = (BeliefSet) obj;
    if (!Arrays.equals(fields, other.fields)) {
      return false;
    }
    String name = getName();
    String oname = other.getName();
    if (name == null) {
      return (oname == null) ? true : false;
    }
    // No check for IDs, therefore if only IDs are different then objects are equal
    return name.equals(oname);
  }

  /**
   *
toString.

   *
   * @return a {@link java.lang.String} object.
   */
  public String toString() {
    return new Gson().toJson(this);
  }
}


package io.github.agentsoz.jill.example.tokenpassing;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.lang.Agent;
import io.github.agentsoz.jill.lang.AgentInfo;
import io.github.agentsoz.jill.util.Log;

import java.io.PrintStream;

/**
 *
TokenAgent2 class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
@AgentInfo(hasGoals = {"io.github.agentsoz.jill.example.tokenpassing.Token2"})
public class TokenAgent2 extends Agent {

  // Defaults
  /** Constant rounds=1. */
  public static int rounds = 1;
  /** Constant out. */
  public static PrintStream out;

  /**
   *
Constructor for TokenAgent2.

   *
   * @param str a {@link java.lang.String} object.
   */
  public TokenAgent2(String str) {
    super(str);
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public void start(PrintStream writer, String[] params) {
    parse(params);
    out = writer;
    if (getId() == 0) {
      Log.debug("round 1");
      send(1, new Token2(1, 1));
    }
  }

  /**
   * Parses the command line arguments.
   *
   * @param args the command line arguments
   */
  public static void parse(String[] args) {
    for (int i = 0; i < args.length; i++) {
      if ("-rounds".equals(args[i]) && i + 1 < args.length) {
        i++;
        try {
          rounds = Integer.parseInt(args[i]);
        } catch (NumberFormatException e) {
          Log.warn("Value '" + args[i] + "' is not a number");
        }
      }
    }
  }
}


package io.github.agentsoz.jill.example.greeter;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.core.beliefbase.BeliefBaseException;
import io.github.agentsoz.jill.core.beliefbase.BeliefSetField;
import io.github.agentsoz.jill.lang.Agent;
import io.github.agentsoz.jill.lang.AgentInfo;
import io.github.agentsoz.jill.util.Log;

import java.io.PrintStream;
import java.util.Random;

/**
 *
Greeter class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
@AgentInfo(hasGoals = {"io.github.agentsoz.jill.example.greeter.BeFriendly"})
public class Greeter extends Agent {

  // Defaults
  private static Random rand = new Random();
  private static int numNeighbours = 1;
  private static final String beliefset = "neighbour";

  private static final String[] males =
      {"Alex", "Daniel", "John", "Lionel", "Nick", "Oscar", "Paul", "Rod", "Sam", "Tom"};
  private static final String[] females =
      {"Alice", "Elisa", "Fiona", "Julia", "Kate", "Laura", "Margaret", "Nancy", "Pam", "Rachael"};
  private static final String[] middle =
      {"A.", "B.", "C.", "D.", "E.", "F.", "G.", "H.", "I.", "J.", "K.", "L.", "M.", "N.", "O.",
          "P.", "Q.", "R.", "S.", "T.", "U.", "V.", "W.", "X.", "Y.", "Z."};
  private static final String[] surnames = {"Anderson", "Brown", "Jones", "Martin", "Morton",
      "Smith", "Taylor", "White", "Williams", "Wilson",};

  /**
   *
Constructor for Greeter.

   *
   * @param name a {@link java.lang.String} object.
   */
  public Greeter(String name) {
    super(name);
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public void start(PrintStream writer, String[] params) {
    // Parse the arguments
    parse(params);

    // Create a new belief set about neighbours
    BeliefSetField[] fields = {new BeliefSetField("name", String.class, true),
        new BeliefSetField("gender", String.class, false),};

    try {
      // Attach this belief set to this agent
      this.createBeliefSet(beliefset, fields);

      // Add beliefs about neighbours
      registerNeighbours(rand, numNeighbours);
      Log.debug("Agent " + getName() + " is initialising with " + numNeighbours + " neighbours");

      // Post the goal to be friendly
      post(new BeFriendly("BeFriendly"));
    } catch (BeliefBaseException e) {
      Log.error(e.getMessage());
    }
  }

  /**
   * Helper function to add beliefs about neighbours.
   *
   * @param rand random number generator to use
   * @param count number of beliefs to add
   * @throws BeliefBaseException thrown if there is a belief base access error
   */
  private void registerNeighbours(Random rand, int count) throws BeliefBaseException {
    int size = (count < 0) ? 0 : count;
    for (int i = 0; i < size; i++) {
      boolean male = (rand.nextDouble() < 0.5) ? true : false;
      this.addBelief(beliefset, buildName(male), male ? "male" : "female");
    }
  }

  /**
   * Builds a new name.
   *
   * @param male specifies whether the name should be for a male or female
   * @return the constructed name
   */
  private static String buildName(boolean male) {
    StringBuilder name = new StringBuilder();
    name.append(male ? males[rand.nextInt(males.length)] : females[rand.nextInt(females.length)])
        .append(' ').append(middle[rand.nextInt(middle.length)]).append(' ')
        .append(surnames[rand.nextInt(surnames.length)]);
    return name.toString();
  }

  /**
   * Parses the command line arguments.
   *
   * @param args arguments passed to this program
   */
  public static void parse(String[] args) {
    for (int i = 0; i < args.length; i++) {
      switch (args[i]) {
        case "-seed":
          if (i + 1 < args.length) {
            i++;
            int seed = 0;
            try {
              seed = Integer.parseInt(args[i]);
              rand = new Random(seed);
            } catch (NumberFormatException e) {
              Log.warn("Seed value '" + args[i] + "' is not a number");
            }
          }
          break;
        case "-neighbourhoodSize":
          if (i + 1 < args.length) {
            i++;
            try {
              numNeighbours = Integer.parseInt(args[i]);
            } catch (NumberFormatException e) {
              Log.warn("Neighbourhood size value '" + args[i] + "' is not a number");
            }
          }
          break;
        default:
          // Ignore any other arguments
          break;
      }
    }
  }
}


package io.github.agentsoz.jill.core;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.Main;
import io.github.agentsoz.jill.config.GlobalConstant;
import io.github.agentsoz.jill.core.beliefbase.Belief;
import io.github.agentsoz.jill.lang.Agent;
import io.github.agentsoz.jill.lang.Goal;
import io.github.agentsoz.jill.lang.Plan;
import io.github.agentsoz.jill.lang.PlanBindings;
import io.github.agentsoz.jill.lang.PlanStep;
import io.github.agentsoz.jill.struct.GoalType;
import io.github.agentsoz.jill.struct.PlanType;
import io.github.agentsoz.jill.util.Log;
import io.github.agentsoz.jill.util.Stack255;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;

/**
 *
IntentionSelector class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
public class IntentionSelector implements Runnable {

  private final Logger logger = LoggerFactory.getLogger(Main.LOGGER_NAME);

  private final int poolid;
  private final Set<Integer> activeAgents;
  Set<Integer> extToRemove;
  Set<Integer> extToAdd;

  private final Object lock;
  private boolean hasMessage;
  private boolean isIdle;
  private boolean shutdown;
  private final PlanBindings bindings; // plan bindings

  /**
   * Constructs a new intention selector to manage a set of agents.
   *
   * @param poolid ID of this pool (must follow the sequence 0,1,2,3,...).
   * @param seed to initialise the random number generator
   */
  public IntentionSelector(int poolid, long seed) {
    this.poolid = poolid;
    this.lock = new Object();
    this.hasMessage = false;
    this.isIdle = false;
    this.shutdown = false;
    activeAgents = new HashSet<Integer>();
    extToRemove = new HashSet<Integer>();
    extToAdd = new HashSet<Integer>();
    bindings = new PlanBindings(new Random(seed));
  }

  /**
   * Runs this intentions selction thread.
   */
  public void run() {
    Set<Integer> toRemove = new HashSet<Integer>();
    do {
      boolean idle = true;
      // Remove agents that have have become idle due to an external event
      removeInactiveAgents();
      // Add agents that have have become active due to an external event
      addActiveAgents();
      for (Integer i : activeAgents) {
        Agent agent = (Agent) GlobalState.agents.get(i);
        Stack255 agentExecutionStack = (Stack255) (agent).getExecutionStack();
        if (!isStackValid(agent, agentExecutionStack)) {
          // Mark this agent for removal
          toRemove.add(i);
          continue;
        }
        // At least one agent is active
        idle = false;
        // Get the item at the top of the stack
        Object node = (Object) agentExecutionStack.get((byte) (agentExecutionStack.size() - 1));
        if (node instanceof Plan) {
          // If it is a plan then execute a plan step; and if it finished then remove it
          managePlan(i, agentExecutionStack, (Plan) node, toRemove);
        } else if (node instanceof Goal) {
          // If it is a goal then find a plan for it and put it on the stack
          manageGoal(i, agent, agentExecutionStack, (Goal) node);
        }
        agent.nextActiveStack(); // select the next active stack for next time
      }
      // remove agents that have finished executing plans and have gone idle in this cycle
      removeFinishedAgents(toRemove);
      if (idle) {
        waitOnExternalMessage();
        if (shutdown) {
          break;
        }
      }
    }
    while (true);
    logger.debug("Pool {} is exiting", poolid);
  }

  /**
   * Checks if this agent's execution stack is valid. The stack is valid if it is not null or empty
   * and has not exceeded the maximum size limit of 255.
   *
   * @param agent the agent whose stack is to be checked
   * @param agentExecutionStack that agent's execution stack
   * @return true if stack is valid, false otherwise
   */
  private boolean isStackValid(Agent agent, Stack255 agentExecutionStack) {
    if (agentExecutionStack == null) {
      return false;
    }
    final int esSize = agentExecutionStack.size();
    logger.trace("{} execution stack is {}/255 full", Log.logPrefix(agent.getId()), esSize);
    if (esSize == 0) {
      return false;
    }
    if (esSize >= 255) {
      logger.error("{} execution stack reached size limit of 255. Cannot continue.",
          Log.logPrefix(agent.getId()));
      return false;
    }
    return true;
  }

  /**
   * Removes the given list of agents from the list of active agents.
   *
   * @param toRemove the list of agent IDs to remove
   */
  private void removeFinishedAgents(Set<Integer> toRemove) {
    if (!toRemove.isEmpty()) {
      for (int i : toRemove) {
        activeAgents.remove(i);
      }
      toRemove.clear();
    }
  }

  /**
   * Manages the goal at the top of the execution stack of an agent. All relevant plans are
   * evaluated to see if their context conditions hold. Plans deemed applicable are then added to
   * the list of bindings from which a plan instane will be eventually selected.
   *
   * @param agentIndex the agent's index
   * @param agent the agent in question
   * @param agentExecutionStack the agent's execution stack
   * @param node the goal at the top of the xecution stack
   * @return false if something went wrong, true otherwise
   */
  private boolean manageGoal(int agentIndex, Agent agent, Stack255 agentExecutionStack, Goal node) {
    // Get the goal type for this goal
    GoalType gtype = (GoalType) GlobalState.goalTypes.find(node.getClass().getName());
    byte[] ptypes = gtype.getChildren();
    assert (ptypes != null);
    // Clear any previous plan bindings before adding any new ones
    bindings.clear();
    for (int p = 0; p < ptypes.length; p++) {
      PlanType ptype = (PlanType) GlobalState.planTypes.get(ptypes[p]);

      try {
        // Create an object of this Plan type, so we can
        // access its context condition
        Plan planInstance =
            (Plan) (ptype.getPlanClass().getConstructor(Agent.class, Goal.class, String.class)
                .newInstance(GlobalState.agents.get(agentIndex), node, "p"));
        // Clear previously buffered context results if any
        agent.clearLastResults();
        // Evaluate the context condition
        if (planInstance.context()) {
          // Get the results of context query just performed
          Set<Belief> results = agent.getLastResults();
          // Add the results to the bindings
          bindings.add(planInstance, (results == null) ? null : new LinkedHashSet<Belief>(results));
        }
      } catch (NoSuchMethodException | SecurityException | InstantiationException
          | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        logger.error("Could not create plan object of type " + ptype.getClass().getName(), e);
      }
    }
    int numBindings = bindings.size();
    if (numBindings == 0) {
      // No plan options for this goal at this point in time, so move to the next agent
      logger.debug(Log.logPrefix(agent.getId()) + " has no applicable plans for goal " + gtype
          + " and will continue to wait indefinitely");
      return false;
    }
    // Call the meta-level planning prior to plan selection
    agent.notifyAgentPrePlanSelection(bindings);
    // Pick a plan option using specified policy
    Plan planInstance = bindings.selectPlan(GlobalConstant.PLAN_SELECTION_POLICY);
    // Now push the plan on to the intention stack
    synchronized (agentExecutionStack) {
      logger.debug(Log.logPrefix(agent.getId()) + " choose an instance of plan "
          + planInstance.getClass().getSimpleName() + " to handle goal "
          + node.getClass().getSimpleName());
      agentExecutionStack.push(planInstance);
    }
    return true;
  }

  /**
   * Manages the plan at the top of this agent's execution stack. If the plan has finished it is
   * removed, else it is progresses by a single {@link PlanStep}.
   *
   * @param agentIndex the agent in question
   * @param agentExecutionStack this agent's execution stack
   * @param node the plan at the top of the execution stack
   * @param toRemove the remove list to which this agent should be added if the plan has finished
   */
  private void managePlan(int agentIndex, Stack255 agentExecutionStack, Plan node,
      Set<Integer> toRemove) {
    // If done then pop this plan/goal
    if (node.hasfinished()) {
      logger.debug(Log.logPrefix(agentIndex) + " finished executing plan "
          + node.getClass().getSimpleName());
      synchronized (agentExecutionStack) {
        // Pop the plan off the stack
        agentExecutionStack.pop();
        // Pop the goal off the stack
        agentExecutionStack.pop();
        if (agentExecutionStack.isEmpty()) {
          // remove empty intention stacks
          Agent agent = (Agent) GlobalState.agents.get(agentIndex);
          int size = agent.cleanupStacks();
          // If we are left with only one stack and that is empty, then agent is idle
          if (size == 1 && agent.getExecutionStack().isEmpty()) {
            // Mark this agent as idle
            // Main.setAgentIdle(i, true);
            toRemove.add(agentIndex);
          }
        }
      }
    } else {
      logger.debug(Log.logPrefix(agentIndex) + " is executing a step of plan "
          + node.getClass().getSimpleName());
      node.step();
    }
  }

  /**
   * Removes from {@link #activeAgents} all agents flagged by external events as having become
   * inactive.
   */
  private void removeInactiveAgents() {
    synchronized (extToRemove) {
      if (!extToRemove.isEmpty()) {
        for (int i : extToRemove) {
          activeAgents.remove(i);
        }
        extToRemove.clear();
      }
    }
  }

  /**
   * Adds to {@link #activeAgents} all agents flagged by external events as having become active.
   */
  private void addActiveAgents() {
    synchronized (extToAdd) {
      if (!extToAdd.isEmpty()) {
        for (int i : extToAdd) {
          activeAgents.add(i);
        }
        extToAdd.clear();
      }
    }
  }

  /**
   * Waits on {@link IntentionSelector#lock} until woken up by an external message.
   */
  private void waitOnExternalMessage() {
    synchronized (lock) {
      while (!hasMessage) {
        try {
          logger.debug("Pool {} is idle; will wait on external message", poolid);
          // Main.incrementPoolsIdle();
          isIdle = true;
          Main.flagPoolIdle();
          lock.wait();
          isIdle = false;
          // Main.decrementPoolsIdle();
          logger.debug("Pool {} just woke up on external message", poolid);
        } catch (InterruptedException e) {
          logger.error("Pool " + poolid + " failed to wait on external message: ", e);
        }
      }
      hasMessage = false;
    }
  }

  /**
   * Flags to this intention selection thread that an external message, to an agent managed by this
   * thread, is waiting to be processed.
   */
  public void flagMessage() {
    synchronized (lock) {
      logger.debug("Pool {} received a new message", poolid);
      hasMessage = true;
      lock.notify(); // NOPMD - ignore notifyall() warning
    }
  }

  /**
   * Checks if this intention selector is idle.
   *
   * @return true if idle, false otherwise
   */
  public boolean isIdle() {
    return isIdle && !hasMessage;
  }

  /**
   * Terminates this intention selector thread.
   */
  public void shutdown() {
    synchronized (lock) {
      logger.debug("Pool {} received shutdown message", poolid);
      shutdown = true;
      hasMessage = true;
      lock.notify(); // NOPMD - ignore notifyall() warning
    }
  }

  /**
   * Sets the idle status of the given agent managed by this intention selector.
   *
   * @param agentId ID of the agent whose idle status is being set
   * @param idle the new idle status of this agent
   */
  // FIXME: Threading issue when external threads changes activeagents
  // and this thread is still iterating over activeagents
  public void setAgentIdle(int agentId, boolean idle) {
    // If agent is becoming active, and not already active
    if (!idle /* && !activeAgents.contains(agentId) */) {
      synchronized (extToAdd) {
        extToAdd.add(agentId);
      }
    }
    // If agent is becoming idle, and not already idle
    if (idle /* && activeAgents.contains(agentId) */) {
      synchronized (extToRemove) {
        extToRemove.add(agentId);
      }
    }
  }
}


package io.github.agentsoz.jill.example.hanoi;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 *
Board class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
public class Board {

  @SuppressWarnings("unchecked")
  List<Integer>[] pins = (ArrayList<Integer>[]) (new ArrayList[3]);

  /**
   * Creates a new game board.
   *
   * @param ndiscs number of discs in the game
   */
  public Board(int ndiscs) {
    for (int i = 0; i < pins.length; i++) {
      pins[i] = new ArrayList<Integer>();
    }
    for (int i = ndiscs; i > 0; i--) {
      pins[0].add(i);
    }

  }

  /**
   * Move a disc from pin A to pin B.
   *
   * @param pinA the ID of the source pin
   * @param pinB the ID of the destination pin
   * @return true if the move was made, false otherwise
   */
  public boolean move(int pinA, int pinB) {
    // Nothing to do if the pin number is invalid
    if (pinA < 0 || pinA >= pins.length || pinB < 0 || pinB >= pins.length) {
      Log.warn("Invalid board pin specified " + pinA + ". Should be between 0.." + (pins.length - 1)
          + " (inclusive).");
      return false;
    } else if (pins[pinA].isEmpty()) {
      Log.warn("No disc on pin" + pinA);
      return false;
    } else if (pinA == pinB) {
      Log.info("Moving disc from pin" + pinA + " on to itself (means the board will not change)");
      return true;
    }
    int discOnA = pins[pinA].get(pins[pinA].size() - 1);
    int discOnB =
        (pins[pinB].isEmpty()) ? Integer.MAX_VALUE : pins[pinB].get(pins[pinB].size() - 1);
    if (discOnB < discOnA) {
      Log.warn("Cannot move disc" + discOnA + " (pin" + pinA + ") on to smaller disc" + discOnB
          + " (pin" + pinB + ")");
      return false;
    }
    pins[pinB].add(pins[pinA].remove(pins[pinA].size() - 1));
    return true;
  }

  /**
   * {@inheritDoc}.
   */
  @Override
  public String toString() {
    StringBuilder str = new StringBuilder();
    if (pins.length == 0) {
      return str.toString();
    }
    for (int i = 0; i < pins.length; i++) {
      if (pins[i].isEmpty()) {
        str.append('|');
      }
      for (Integer j : pins[i]) {
        str.append('|');
        str.append(j);
      }
      str.append('\n');
    }
    return str.substring(0, str.length() - 1);
  }

}


package io.github.agentsoz.jill;

/*
 * #%L
 * Jill Cognitive Agents Platform
 * %%
 * Copyright (C) 2014 - 2019 by its authors. See AUTHORS file.
 * %%
 * This program 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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 */

import io.github.agentsoz.jill.config.Config;
import io.github.agentsoz.jill.core.GlobalState;
import io.github.agentsoz.jill.core.IntentionSelector;
import io.github.agentsoz.jill.core.ProgramLoader;
import io.github.agentsoz.jill.core.beliefbase.abs.ABeliefStore;
import io.github.agentsoz.jill.lang.Agent;
import io.github.agentsoz.jill.lang.JillExtension;
import io.github.agentsoz.jill.util.AObjectCatalog;
import io.github.agentsoz.jill.util.ArgumentsLoader;
import io.github.agentsoz.jill.util.Log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
Main class.

 *
 * @author dsingh
 * @version $Id: $Id
 */
public final class Main {

  /** Constant LOGGER_NAME="io.github.agentsoz.jill". */
  public static final String LOGGER_NAME = "io.github.agentsoz.jill";
  /** Constant logger. */
  public static Logger logger;

  /**
   * Used to mark if all the pools of agents are idle or not.
   */
  static int npools;
  static int poolsize;
  static IntentionSelector[] intentionSelectors;
  /** Constant poolsIdle. */
  public static AtomicInteger poolsIdle = new AtomicInteger();
  private static PrintStream writer;

  private static final String agentsIn = " agents in ";

  /**
   * This class cannot be instantiated.
   */
  private Main() {

  }

  /**
   * Program entry
   *
   * @param args command line arguments.
   */
  public static void main(String[] args) {
    // Parse the command line options
    ArgumentsLoader.parse(args);

    // Load the configuration
    Config config = ArgumentsLoader.getConfig();

    // Initialise the system with the given arguments
    if (!init(config)) {
      return;
    }

    // load all extensions
    if (!Program.loadExtensions(config)) {
      return;
    }

    // Start the engine
    start(config);

    // Wait until the agents become idle
    waitUntilIdle();

    // finish up
    finish();
  }

  /**
   * Initialises the Jill engine.
   *
   * @param config a valid loaded configuration
   * @return a boolean.
   */
  public static boolean init(Config config) {

    // Pause for key press from user if requested
    if (config.isDoPauseForUserInput()) {
      Program.pauseForUserInput();
    }

    // Configure logging
    Log.createLogger(Main.LOGGER_NAME, config.getLogLevel(), config.getLogFile());
    logger = LoggerFactory.getLogger(Main.LOGGER_NAME);

    int numAgents = 0;
    for (Config.AgentTypeData agentType : config.getAgents()) {
      numAgents += agentType.getCount();
    }
    final int increment = 10000;

    GlobalState.reset();
    GlobalState.agents = new AObjectCatalog("agents", numAgents, increment);

    // Create the central belief base
    GlobalState.beliefbase = new ABeliefStore(numAgents, config.getNumThreads());
    long t0;

    // Create the agents
    t0 = System.currentTimeMillis();
    for (Config.AgentTypeData agentType : config.getAgents()) {
      if (!ProgramLoader.loadAgent(agentType.getClassname(), agentType.getCount(),
          GlobalState.agents)) {
        // return unsuccessful
        return false;
      }
    }
    long t1 = System.currentTimeMillis();
    logger.info("Created " + GlobalState.agents.size() + agentsIn + Log.formattedDuration(t0, t1));

    // Initialise the thread pools
    Program.initIntentionSelectionPools(numAgents, config.getNumThreads());

    // Redirect the agent program output if specified
    if (config.getProgramOutputFile() != null) {
      try {
        writer = new PrintStream(config.getProgramOutputFile(), "UTF-8");
      } catch (FileNotFoundException | UnsupportedEncodingException e) {
        logger.error("Could not open program outout file " + config.getProgramOutputFile(), e);
      }
    } else {
      writer = System.out;
    }

    // Initialise the intention selection threads
    Program.initIntentionSelectionThreads(config);

    // return success
    return true;
  }

  /**
   * Starts the Jill engine. Must have previously been initialised (see {@link #init(Config)}).
   *
   * @param config a valid config
   */
  public static void start(Config config) {
    // Start the agents
    long t0 = System.currentTimeMillis();
    int index = 0;
    int subIndex = 0;
    for (Config.AgentTypeData agentType : config.getAgents()) {
      index = subIndex;
      String[] args = (agentType.getArgs() == null || agentType.getArgs().isEmpty()) ? new String[0]
          : agentType.getArgs().toArray(new String[agentType.getArgs().size()]);
      for (subIndex = index; subIndex < index + agentType.getCount(); subIndex++) {
        // Get the agent
        Agent agent = (Agent) GlobalState.agents.get(subIndex);
        // Start the agent
        agent.start(writer, args);
      }
    }
    long t1 = System.currentTimeMillis();
    logger.info("Started " + GlobalState.agents.size() + agentsIn + Log.formattedDuration(t0, t1));

    // Start the intention selection threads
    Program.startIntentionSelectionThreads();

  }

  /**
   * Blocks until all agents have finished executing plans and have gone idle.
   */
  public static void waitUntilIdle() {
    // Wait till we are all done
    long t0 = System.currentTimeMillis();

    synchronized (poolsIdle) {
      while (!arePoolsIdle()) {
        try {
          poolsIdle.wait();
        } catch (InterruptedException e) {
          logger.error("Failed to wait on termination condition: " + e.getMessage());
        }
      }
    }

    long t1 = System.currentTimeMillis();
    logger.info(
        "Finished running " + GlobalState.agents.size() + agentsIn + Log.formattedDuration(t0, t1));
  }

  /**
   * Termiantes the Jill engine.
   */
  public static void finish() {
    // Terminate the extensions first
    for (JillExtension extension : GlobalState.eventHandlers) {
      extension.finish();
    }

    // Now shut down the threads
    Program.shutdownIntentionSelectionThreads();

    // Finish the agents
    long t0 = System.currentTimeMillis();
    for (int i = 0; i < GlobalState.agents.size(); i++) {
      // Get the agent
      Agent agent = (Agent) GlobalState.agents.get(i);
      // Terminate the agent
      agent.finish();
    }
    // Close the writer
    if (writer != null) {
      writer.close();
    }
    long t1 = System.currentTimeMillis();
    logger
        .info("Terminated " + GlobalState.agents.size() + agentsIn + Log.formattedDuration(t0, t1));
  }

  /**
   * Checks if the system is idle, i.e., all the agents pools are idle
   *
   * @return true if idle, false otherwise
   */
  public static boolean arePoolsIdle() {
    boolean idle = true;
    for (int i = 0; i < npools; i++) {
      idle &= (intentionSelectors[i] == null) || intentionSelectors[i].isIdle();
    }
    return idle;
  }

  /**
   * Checks if the {@link io.github.agentsoz.jill.core.IntentionSelector} pools have finished,
   * i.e., all agents in the pools are idle.
   *
   * @return true if pools have finished, false otherwise
   */
  public static boolean arePoolsFinished() {
    boolean finished = true;
    for (int i = 0; i < intentionSelectors.length; i++) {
      finished &= (intentionSelectors[i] == null);
    }
    return finished;
  }

  /**
   * Notifies all threads waiting on {@link #poolsIdle}.
   */
  public static void flagPoolIdle() {
    synchronized (poolsIdle) {
      poolsIdle.notifyAll();
    }
  }

  /**
   * Sets a bit in the agentsIdle cache, to mark if this agent is idle (or not).
   *
   * @param agentId the agent is question
   * @param isIdle the new idle status of this agent
   */
  public static void setAgentIdle(int agentId, boolean isIdle) {
    int poolid = poolid(agentId);
    intentionSelectors[poolid].setAgentIdle(agentId, isIdle);
  }

  /**
   * Gets the ID of the intention selection pool to which the given agent belongs.
   *
   * @param agentid ID of the agent for which the pool ID is being requested
   * @return ID of the pool to which this agent belongs
   */
  public static int poolid(int agentid) {
    int poolid = agentid / poolsize;
    if (poolid + 1 > npools) {
      poolid = npools - 1;
    }
    return poolid;
  }

  /**
   * Flags to the given intentions selection pool that an external message is waiting to be
   * processed.
   *
   * @param toPool ID of the intention selection pool
   */
  public static void flagMessageTo(int toPool) {
    intentionSelectors[toPool].flagMessage();
  }
}


/*
 * Copyright © 2013-2018, The SeedStack authors <http://seedstack.org>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.seedstack.jpa.internal.specification;

import com.google.common.base.Preconditions;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Root;

public class JpaTranslationContext<T> {
    private final CriteriaBuilder criteriaBuilder;
    private final Root<T> root;
    private Expression<?> expression;

    public JpaTranslationContext(CriteriaBuilder criteriaBuilder, Root<T> root) {
        this.criteriaBuilder = criteriaBuilder;
        this.root = root;
    }

    public JpaTranslationContext(JpaTranslationContext<T> source) {
        this.criteriaBuilder = source.criteriaBuilder;
        this.root = source.root;
        this.expression = source.expression;
    }

    public CriteriaBuilder getCriteriaBuilder() {
        return criteriaBuilder;
    }

    /**
     * Returns the JPA root entity used to build the criteria.
     *
     * @return the JPA root entity.
     */
    public Root<T> getRoot() {
        return root;
    }

    /**
     * Returns the expression currently active in the building context.
     *
     * @param <E> the type of the expression.
     * @return the JPA expression.
     */
    @SuppressWarnings("unchecked")
    public <E> Expression<E> pickExpression() {
        Preconditions.checkState(this.expression != null, "No expression has been set");
        Expression<E> result = (Expression<E>) this.expression;
        expression = null;
        return result;
    }

    /**
     * Sets the expression currently active in the building context.
     *
     * @param expression the expression.
     * @param <E>        the type of the expressions.
     */
    public <E> void setExpression(Expression<E> expression) {
        Preconditions.checkState(this.expression == null, "An expression is already set");
        this.expression = expression;
    }
}


/*
 * Copyright © 2013-2018, The SeedStack authors <http://seedstack.org>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.seedstack.jpa.internal;

import javax.persistence.criteria.Expression;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import org.seedstack.seed.SeedException;

public final class JpaUtils {
    private JpaUtils() {
        // no instantiation allowed
    }

    /**
     * Create a new join or find an existing join on the specified attribute.
     *
     * @param attribute The attribute to join on.
     * @param from      The entity from.
     * @return the join expression.
     */
    public static Expression<?> join(String attribute, From<?, ?> from) {
        Expression<?> path;
        try {
            String[] properties = attribute.split("\\.");
            if (properties.length > 1) {
                path = joinRecursively(properties, findOrCreateJoin(properties[0], from), 1)
                        .get(properties[properties.length - 1]);
            } else {
                path = from.get(properties[0]);
            }
        } catch (IllegalArgumentException e) {
            throw SeedException.wrap(e, JpaErrorCode.UNABLE_TO_CREATE_JPA_JOIN_FOR_SPECIFICATION)
                    .put("property", attribute);
        }
        return path;
    }

    /**
     * Join recursively using the split property path.
     *
     * @param properties property path.
     * @param join       current join.
     * @param index      index of the current property to be joined.
     * @return the resulting join.
     */
    private static Join<?, ?> joinRecursively(String[] properties, Join<?, ?> join, int index) {
        if (index < properties.length - 1) {
            return joinRecursively(properties, findOrCreateJoin(properties[index], join), index + 1);
        } else {
            return join;
        }
    }

    /**
     * Find an existing join for the property or create a new join.
     *
     * @param property property to be joined
     * @param from     clause letting us keep track of all already existing joins
     * @return the join
     */
    private static Join<?, ?> findOrCreateJoin(String property, From<?, ?> from) {
        for (Join<?, ?> rootJoin : from.getJoins()) {
            if (rootJoin.getAttribute().getName().equals(property)) {
                return rootJoin;
            }
        }
        return from.join(property);
    }
}


package js.container;

import js.log.Log;
import js.log.LogFactory;

/**
 * Dump managed instance to {@link Container} class logger. This processor dumps instance managed class to container logger but
 * only if scope is {@link InstanceScope#APPLICATION} and type {@link InstanceType#PROXY}. This processor does not update
 * instance and does not throw any exception.
 * <p>
 * This processor is for debugging and works on behalf of container, that is, uses container class logger to write dump record.
 *
 * @author Iulian Rotaru
 * @version final
 */
final class LoggerInstanceProcessor implements InstanceProcessor {
	/** Class logger. */
	private static final Log log = LogFactory.getLog(LoggerInstanceProcessor.class);

	/**
	 * Dump managed instance of {@link InstanceType#PROXY} type and with {@link InstanceScope#APPLICATION} scope to container
	 * logger. For managed instance of other scope / type combinations this method does nothing.
	 *
	 * @param managedClass managed class,
	 * @param instance instance of managed class.
	 */
	@Override
	public void postProcessInstance(ManagedClassSPI managedClass, Object instance) {
		if (!managedClass.getInstanceScope().equals(InstanceScope.APPLICATION)) {
			return;
		}
		if (!managedClass.getInstanceType().equals(InstanceType.PROXY)) {
			return;
		}

		Class<?>[] interfaceClasses = managedClass.getInterfaceClasses();
		StringBuilder interfaceNames = new StringBuilder(interfaceClasses[0].getName());
		for (int i = 1; i < interfaceClasses.length; ++i) {
			interfaceNames.append(", ");
			interfaceNames.append(interfaceClasses[i].getName());
		}

		log.debug("Create managed container proxy:\r\n" + //
				"\t- implementation: %s\r\n" + //
				"\t- interface(s): %s\r\n" + //
				"\t- scope: %s\r\n" + //
				"\t- type: %s\r\n" + //
				"\t- transactional: %s\r\n" + //
				"\t- remote: %s", managedClass.getImplementationClass(), interfaceNames.toString(), managedClass.getInstanceScope(), managedClass.getInstanceType(), managedClass.isTransactional(), managedClass.isRemotelyAccessible());
	}
}


package net.typedrest;

import java.util.List;
import lombok.Data;

/**
 * Represents a subset of a set of elements.
 *
 * @param <TEntity> The type of element the response contains.
 */
@Data
public class PartialResponse<TEntity> {

    /**
     * The returned elements.
     */
    private final List<TEntity> elements;

    /**
     * The index of the first element. null if unset.
     */
    private final Long from;

    /**
     * The index of the last element. null if unset.
     */
    private final Long to;

    /**
     * The total count of elements available on the server. null if
     * unset.
     */
    private final Long length;

    /**
     * Indicates whether the response reaches the end of the elements available
     * on the server.
     *
     * @return true if there are no more elements.
     */
    public boolean isEndReached() {
        if (to == null || from == null) {
            // No range specified, must be complete response
            return true;
        }

        if (length == null) {
            // No lenth specified, can't be end
            return false;
        }

        return to == length - 1;
    }
}


package js.http.encoder;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import javax.servlet.http.HttpServletRequest;

import js.http.form.Form;
import js.http.form.FormImpl;
import js.http.form.FormIterator;
import js.http.form.FormIteratorImpl;
import js.http.form.FormObject;
import js.http.form.Part;
import js.http.form.UploadStream;
import js.http.form.UploadedFile;
import js.lang.IllegalArgumentException;
import js.util.Files;

/**
 * Net method invocation arguments reader for multipart form. This arguments reader expects a multipart form on HTTP request. If
 * there is no valid form on request throws illegal arguments. Parses the form field(s), create an instance of requested formal
 * type and fill it with fields value(s).
 *

 * This arguments reader is used only for methods with single argument; if given formal parameters has not a single item throws
 * illegal argument exception. Current implementation recognizes next types: {@link Form}, {@link FormIterator},
 * {@link UploadedFile}, {@link UploadStream}, {@link InputStream} and {@link FormObject}.
 *

 * This arguments reader implementation is heavily based on {@link js.http.form} package.
 *
 * @author Iulian Rotaru
 * @version final
 */
final class MultipartFormArgumentsReader implements ArgumentsReader {
	/**
	 * Store stream argument, if any, so that to be able to close it after method execution. Do not store stream argument as
	 * field of this arguments reader since instance is reused and cannot have state.
	 */
	private final ThreadLocal<Closeable> threadLocal = new ThreadLocal<Closeable>();

	/**
	 * Parse multipart form from HTTP request, create object instance of requested type and fill it with form field value(s). If
	 * formal type is a stream store it to local thread so that to be able to close it after arguments processed by application
	 * code.
	 *
	 * @param httpRequest HTTP request,
	 * @param formalParameters formal parameter types.
	 * @return single item arguments array.
	 * @throws IOException if reading from HTTP request input stream fails.
	 * @throws IllegalArgumentException if formal parameters does not contain exactly one type.
	 * @throws IllegalArgumentException if formal parameter type is parameterized.
	 * @throws IllegalArgumentException if HTTP request does not contain a valid multipart form.
	 */
	@Override
	public Object[] read(HttpServletRequest httpRequest, Type[] formalParameters) throws IOException, IllegalArgumentException {
		if (formalParameters.length != 1) {
			throw new IllegalArgumentException("Bad parameters count. Should be exactly one but is |%d|.", formalParameters.length);
		}
		if (formalParameters[0] instanceof ParameterizedType) {
			throw new IllegalArgumentException("Parameterized type |%s| is not supported.", formalParameters[0]);
		}

		Class<?> type = (Class<?>) formalParameters[0];
		Object[] arguments = new Object[1];

		if (type.equals(Form.class)) {
			arguments[0] = new FormImpl(httpRequest);
		}

		else if (type.equals(FormIterator.class)) {
			arguments[0] = new FormIteratorImpl(httpRequest);
		}

		else if (type.equals(UploadedFile.class)) {
			Form form = new FormImpl(httpRequest);
			// Form#getUploadedFile() throws IlegalArgumentException if form has not a single file upload part
			arguments[0] = form.getUploadedFile();
		}

		else if (type.equals(UploadStream.class)) {
			threadLocal.set((Closeable) (arguments[0] = getUploadStream(httpRequest, formalParameters)));
		}

		else if (type.equals(InputStream.class)) {
			threadLocal.set((Closeable) (arguments[0] = getUploadStream(httpRequest, formalParameters).openStream()));
		}

		else {
			arguments[0] = new FormObject(httpRequest, type).getValue();
		}

		return arguments;
	}

	/** Close stream from current thread storage then remove the storage. */
	@Override
	public void clean() {
		Files.close(threadLocal.get());
		threadLocal.remove();
	}

	// --------------------------------------------------------------------------------------------
	// UTILITY METHODS

	/**
	 * Get upload stream from given HTTP request. This method expects on HTTP request a multipart form with a single part of
	 * byte stream type. Returns an upload stream instance wrapping the form stream part.
	 *
	 * @param httpRequest HTTP request,
	 * @param formalParameters used only for exception message.
	 * @return upload stream.
	 * @throws IOException if reading form HTTP request fails.
	 * @throws IllegalArgumentException if there is no valid multipart form on HTTP request.
	 */
	private static UploadStream getUploadStream(HttpServletRequest httpRequest, Type[] formalParameters) throws IOException {
		FormIterator form = new FormIteratorImpl(httpRequest);
		if (!form.hasNext()) {
			throw new IllegalArgumentException("Empty form.");
		}
		Part part = form.next();
		if (!(part instanceof UploadStream)) {
			throw new IllegalArgumentException("Illegal form. Expected uploaded stream but got field |%s|.", part.getName());
		}
		return (UploadStream) part;
	}
}


/*
 * Copyright (c) 2002 Cunningham & Cunningham, Inc.
 * Copyright (c) 2009-2015 by Jochen Wierum & Cologne Intelligence
 *
 * This file is part of FitGoodies.
 *
 * FitGoodies is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FitGoodies 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FitGoodies.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.cologneintelligence.fitgoodies.alias;

import java.util.HashMap;
import java.util.Map;

/**
 * Singleton class to manage aliases.
 */
public final class AliasHelper {
	private final Map<String, String> classMapper = new HashMap<>();

	public AliasHelper() {
		initDefaultMappings();
	}

	private void initDefaultMappings() {
		classMapper.put("fitgoodies.ActionFixture", "de.cologneintelligence.fitgoodies.ActionFixture");
		classMapper.put("fitgoodies.ColumnFixture", "de.cologneintelligence.fitgoodies.ColumnFixture");
		classMapper.put("fitgoodies.RowFixture", "de.cologneintelligence.fitgoodies.RowFixture");

		classMapper.put("fitgoodies.file.FileFixture", "de.cologneintelligence.fitgoodies.file.FileFixture");
		classMapper.put("fitgoodies.runners.RunFixture", "de.cologneintelligence.fitgoodies.runners.RunFixture");
		classMapper.put("fitgoodies.selenium.SeleniumFixture", "de.cologneintelligence.fitgoodies.selenium.SeleniumFixture");
		classMapper.put("fitgoodies.selenium.SetupFixture", "de.cologneintelligence.fitgoodies.selenium.SetupFixture");
		classMapper.put("fitgoodies.references.SetupFixture", "de.cologneintelligence.fitgoodies.references.SetupFixture");
		classMapper.put("fitgoodies.mail.SetupFixture", "de.cologneintelligence.fitgoodies.mail.SetupFixture");
		classMapper.put("fitgoodies.date.SetupFixture", "de.cologneintelligence.fitgoodies.date.SetupFixture");
		classMapper.put("fitgoodies.database.SetupFixture", "de.cologneintelligence.fitgoodies.database.SetupFixture");
		classMapper.put("fitgoodies.typehandler.SetupFixture", "de.cologneintelligence.fitgoodies.typehandler.SetupFixture");

		classMapper.put("fitgoodies.database.ResultSetFixture", "de.cologneintelligence.fitgoodies.database.ResultSetFixture");
		classMapper.put("fitgoodies.database.TableFixture", "de.cologneintelligence.fitgoodies.database.TableFixture");

		classMapper.put("fitgoodies.file.CSVFileRecordFixture", "de.cologneintelligence.fitgoodies.file.CSVFileRecordFixture");
		classMapper.put("fitgoodies.file.DelimiterFileRecordFixture", "de.cologneintelligence.fitgoodies.file.DelimiterFileRecordFixture");
		classMapper.put("fitgoodies.file.FixedLengthFileRecordFixture", "de.cologneintelligence.fitgoodies.file.FixedLengthFileRecordFixture");
		classMapper.put("fitgoodies.file.XMLFileFixture", "de.cologneintelligence.fitgoodies.file.XMLFileFixture");
		classMapper.put("fitgoodies.log4j.LogFixture", "de.cologneintelligence.fitgoodies.log4j.LogFixture");
		classMapper.put("fitgoodies.log4j.SetupFixture", "de.cologneintelligence.fitgoodies.log4j.SetupFixture");
		classMapper.put("fitgoodies.mail.MailFixture", "de.cologneintelligence.fitgoodies.mail.MailFixture");
		classMapper.put("fitgoodies.alias.SetupFixture", "de.cologneintelligence.fitgoodies.alias.SetupFixture");

		classMapper.put("fitgoodies.external.StartFixture", "de.cologneintelligence.fitgoodies.external.StartFixture");
	}

	/**
	 * Looks up a class in the alias map. If className exists as
	 * an alias, it is resolved and returned. If the alias doesn't exist, the
	 * original String is returned.
	 *
	 * @param className class name to look up
	 * @return resolved name of the alias
	 */
	public String getClazz(final String className) {
		String realName = className;

		while (classMapper.containsKey(realName)) {
			realName = classMapper.get(realName);
		}
		return realName;
	}

	/**
	 * Registers a new alias alias for className.
	 *
	 * @param alias     alias to use
	 * @param className class the alias maps on
	 */
	public void register(final String alias, final String className) {
		classMapper.put(alias, className);
	}
}


package js.rest;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import js.annotation.RequestPath;
import js.container.AuthorizationException;
import js.container.ManagedClassSPI;
import js.container.ManagedMethodSPI;
import js.http.ContentType;
import js.http.Resource;
import js.http.encoder.ArgumentsReader;
import js.http.encoder.ArgumentsReaderFactory;
import js.http.encoder.ServerEncoders;
import js.http.encoder.ValueWriter;
import js.http.encoder.ValueWriterFactory;
import js.lang.InvocationException;
import js.log.Log;
import js.log.LogFactory;
import js.servlet.AppServlet;
import js.servlet.RequestContext;
import js.servlet.RequestPreprocessor;
import js.util.Types;

/**
 * Servlet for services invoked by REST requests. This REST servlet is enacted for web services; it is not usable for dynamic
 * resources. REST servlet actually invokes a REST method that executes service logic and may return a value. If present, value
 * is serialized back to client.
 *

 * In order to enable this servlet it should be declared into deployment descriptor. REST servlet is always mapped by path, most
 * common rest prefix; it never uses extensions.
 *
 *

 * 	<servlet>
 * 		<servlet-name>rest-servlet</servlet-name>
 * 		<servlet-class>js.rest.RestServlet</servlet-class>
 * 		<load-on-startup>1</load-on-startup>
 * 	</servlet>
 * 	...
 * 	<servlet-mapping>
 * 		<servlet-name>rest-servlet</servlet-name>
 * 		<url-pattern>/rest/*</url-pattern>
 * 	</servlet-mapping>
 *

 *
 *
Request Routing

 *

 * In order to execute service logic, the REST method should be located. Since servlet mapping always uses map by path, this
 * servlet uses {@link HttpServletRequest#getPathInfo()} to locate REST method. Path info syntax is standard:
 *
 *

 * path-info = ["/" resource] "/" sub-resource ["?" query-string]
 *

 *
 * Resource part identify method declaring class and is optional, in which case default class is used. In this context default
 * class means one without request path. Sub-resource identifies managed method by its request path,
 * {@link ManagedMethodSPI#getRequestPath()}. Note that if managed method is not annotated with {@link RequestPath} it request
 * path is method name converted to dashed case.
 *

 * In order to locate REST method this servlet keeps a cache of methods, {@link #restMethods} initialized eagerly by
 * {@link #init(ServletConfig)}. It maps path info to REST method. When a request is processed a quick lookup identifies the
 * method.
 *
 *

 * Class 0        Class 1                 Class i                 REST Methods Mapper
 * +-----------+  +-----------+           +-----------+           +--------------------+
 * | method 00 |  | method 10 |    ...    | method i0 |           | key 00 : method 00 |
 * | method 01 |  | method 11 |           | method i1 |           | key 01 : method 01 |
 * | ...       |  | ...       |           | ...       |           | ...                |
 * | method 0j |  | method 1k |           | method il |           | key 0j : method 0j |
 * +-----|-----+  +-----|-----+           +-----|-----+           | key 10 : method 10 |
 *       |              |                       +---------------> | key 11 : method 11 |
 *       |              +---------------------------------------> | ...                |
 *       +------------------------------------------------------> | key 1k : method 1k |
 *              Servlet                                           | key i0 : method i0 |
 *              +-----------------------+                         | key i1 : method i1 |
 *              |  +-------------+      |           +-----+       | ...                |
 *              |  | pathInfo    +------------------> key +-----> | key il : method il |
 *              |  +-------------+      |           +-----+       +----------|---------+
 *              |                       |                                    |
 *              |  +---------------+    |                                    |
 *              |  | method.invoke | <---------------------------------------+
 *              |  +---------------+    |
 *              +-----------------------+
 *

 *
 * @author Iulian Rotaru
 * @version experimental
 */
public class RestServlet extends AppServlet {
	/** Java serialization version. */
	private static final long serialVersionUID = 1970024026367020016L;

	/** Class logger. */
	private static final Log log = LogFactory.getLog(RestServlet.class);

	/**
	 * Factory for invocation arguments readers. Create instances to read invocation arguments from HTTP request, accordingly
	 * request content type.
	 */
	private final ArgumentsReaderFactory argumentsReaderFactory;

	/** Factory for return value writers. Create instances to serialize method return value to HTTP response. */
	private final ValueWriterFactory valueWriterFactory;

	/**
	 * REST methods mapped to request paths. A REST method is a remotely accessible method that does not return a
	 * {@link Resource}. This cache is eagerly loaded at this servlet initialization.
	 */
	private final Map<String, ManagedMethodSPI> restMethods = new HashMap<>();

	/** Initialize invocation arguments reader and return value writer factories. */
	public RestServlet() {
		log.trace("RestServlet()");
		// both factories are implemented by the same server encoders
		argumentsReaderFactory = ServerEncoders.getInstance();
		valueWriterFactory = ServerEncoders.getInstance();
	}

	/**
	 * Performed super-initialization by {@link AppServlet#init(ServletConfig)} and loads REST methods cache. A REST method is a
	 * remotely accessible method that does not return a {@link Resource}. Map storage key is generated by
	 * {@link #key(ManagedMethodSPI)} based on managed class and REST method request paths and is paired with retrieval key -
	 * {@link #key(String)}, generated from request path info when method invocation occurs.
	 *
	 * @param config servlet configuration object.
	 * @throws UnavailableException if tiny container is not properly initialized.
	 */
	@Override
	public void init(ServletConfig config) throws UnavailableException {
		super.init(config);
		for (ManagedMethodSPI managedMethod : container.getManagedMethods()) {
			if (!managedMethod.isRemotelyAccessible()) {
				continue;
			}
			if (!Types.isKindOf(managedMethod.getReturnType(), Resource.class)) {
				restMethods.put(key(managedMethod), managedMethod);
			}
		}
	}

	/**
	 * Handle request for a REST resource. Locate REST method based on request path info, execute method and serialize back to
	 * client returned value, if any. See class description for general behavior.
	 *
	 * @param context request context.
	 * @throws IOException if reading from HTTP request or writing to HTTP response fail.
	 */
	@Override
	protected void handleRequest(RequestContext context) throws IOException {
		HttpServletRequest httpRequest = context.getRequest();
		HttpServletResponse httpResponse = context.getResponse();

		ArgumentsReader argumentsReader = null;
		Object value = null;
		ManagedMethodSPI method = null;

		try {
			method = restMethods.get(key(httpRequest.getPathInfo()));
			if (method == null) {
				throw new NoSuchMethodException();
			}

			Type[] formalParameters = method.getParameterTypes();
			argumentsReader = argumentsReaderFactory.getArgumentsReader(httpRequest, formalParameters);
			Object[] arguments = argumentsReader.read(httpRequest, formalParameters);

			Object instance = container.getInstance(method.getDeclaringClass());
			value = method.invoke(instance, arguments);
		} catch (AuthorizationException e) {
			sendUnauthorized(context);
			return;
		} catch (NoSuchMethodException e) {
			sendNotFound(context, e);
			return;
		} catch (IllegalArgumentException e) {
			// there are opinions that 422 UNPROCESSABLE ENTITY is more appropriate response
			// see https://httpstatuses.com/422
			sendBadRequest(context);
			return;
		} catch (InvocationException e) {
			sendError(context, e);
			return;
		} finally {
			if (argumentsReader != null) {
				argumentsReader.clean();
			}
		}

		httpResponse.setCharacterEncoding("UTF-8");

		if (method.isVoid()) {
			// expected servlet container behavior:
			// since there is nothing written to respond to output stream, container either set content length to zero
			// or closes connection signaling end of content
			httpResponse.setStatus(HttpServletResponse.SC_NO_CONTENT);
			return;
		}

		// expected servlet container behavior:
		// if client request connection header is close container uses an internal buffer for serialized JSON, add content
		// length response header based on buffer size and closes connection
		// if client request connection header is not explicitly set to close, container uses an internal buffer for serialized
		// JSON but with limited capacity; if capacity is not exceeded set response content length; if capacity is exceeded
		// switch to chunked transfer

		ContentType contentType = valueWriterFactory.getContentTypeForValue(value);
		httpResponse.setStatus(HttpServletResponse.SC_OK);
		httpResponse.setContentType(contentType.getValue());

		ValueWriter valueWriter = valueWriterFactory.getValueWriter(contentType);
		valueWriter.write(httpResponse, value);
	}

	// --------------------------------------------------------------------------------------------
	// UTILITY METHODS

	/**
	 * Generate storage key for REST methods cache. This key is create from declaring class and managed method request paths and
	 * is used on cache initialization. It is paired with {@link #key(String)} created from request path on actual method
	 * invocation.
	 *

	 * Here is storage key syntax that should be identical with retrieval key. Key has optional resource path and sub-resource
	 * path. Resource path is the declaring class request path, {@link ManagedClassSPI#getRequestPath()} and sub-resource path
	 * is managed method request path, {@link ManagedMethodSPI#getRequestPath()}.
	 *
	 *

	 * key = ["/" resource ] "/" sub-resource
	 * resource = declaring class request path
	 * sub-resource = managed method request path
	 *

	 *
	 * @param restMethod REST method.
	 * @return REST method key.
	 */
	private static String key(ManagedMethodSPI restMethod) {
		StringBuilder key = new StringBuilder();
		if (restMethod.getDeclaringClass().getRequestPath() != null) {
			key.append('/');
			key.append(restMethod.getDeclaringClass().getRequestPath());
		}
		key.append('/');
		key.append(restMethod.getRequestPath());
		return key.toString();
	}

	/**
	 * Generate retrieval key for REST methods cache. This key is used by request routing logic to locate REST method about to
	 * invoke. It is based on request path extracted from request URI, see {@link RequestPreprocessor} and
	 * {@link RequestContext#getRequestPath()} - and should be identical with storage key.
	 *

	 * Retrieval key syntax is identical with storage key but is based on request path, that on its turn is extracted from
	 * request URI. In fact this method just trim query parameters and extension, if any.
	 *
	 *

	 * request-path = ["/" resource] "/" sub-resource ["?" query-string]
	 * key = ["/" resource ] "/" sub-resource
	 * resource = managed class request-path
	 * sub-resource = managed method request-path
	 *

	 *
	 * @param requestPath request path identify REST resource to retrieve.
	 * @return REST method key.
	 */
	private static String key(String requestPath) {
		int queryParametersIndex = requestPath.lastIndexOf('?');
		if (queryParametersIndex == -1) {
			queryParametersIndex = requestPath.length();
		}
		int extensionIndex = requestPath.lastIndexOf('.', queryParametersIndex);
		if (extensionIndex == -1) {
			extensionIndex = queryParametersIndex;
		}
		return requestPath.substring(0, extensionIndex);
	}
}