/*
* Node.java
*
* Created on Jan 16, 2008, 10:57:07 AM
*
* Description: Provides a node in an Albus Hierarchical Control System.
*
* Copyright (C) Jan 16, 2008 Stephen L. Reed.
*
* This program 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 2 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.ahcsSupport.domainEntity;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.persistence.Id;
import net.jcip.annotations.ThreadSafe;
import org.apache.log4j.Logger;
import org.openrdf.model.URI;
import org.texai.ahcsSupport.AHCSConstants;
import org.texai.ahcsSupport.NodeAccess;
import org.texai.ahcsSupport.NodeRuntime;
import org.texai.kb.persistence.CascadePersistence;
import org.texai.kb.persistence.RDFEntity;
import org.texai.kb.persistence.RDFEntityManager;
import org.texai.kb.persistence.RDFPersistent;
import org.texai.kb.persistence.RDFProperty;
/** Provides a node in an Albus Hierarchical Control System.
*
* @author Stephen L. Reed
*/
@ThreadSafe
@RDFEntity(context = "texai:AlbusHierarchicalControlSystemContext")
public class Node implements CascadePersistence, Comparable<Node> {
/** the serial version UID */
private static final long serialVersionUID = 1L;
/** the id assigned by the persistence framework */
@Id
private URI id; // NOPMD
/** the node type URI, note that NodeType is persisted in a different repository */
@RDFProperty()
private final URI nodeTypeURI;
/** the node type name */
@RDFProperty()
private final String nodeTypeName;
/** the node nickname */
@RDFProperty()
private String nodeNickname;
/** the roles */
@RDFProperty(predicate = "texai:ahcsNode_role")
private final Set<Role> roles = new HashSet<>();
/** the state values */
@RDFProperty()
private final Set<StateValueBinding> stateValueBindings = new HashSet<>();
/** the node state variable dictionary, state variable name --> state value binding */
private final Map<String, StateValueBinding> stateVariableDictionary = new HashMap<>();
/** the logger */
public static final Logger LOGGER = Logger.getLogger(Node.class);
/** the role id dictionary, role id --> role */
private final Map<URI, Role> roleIdDictionary = new HashMap<>();
/** the role type name dictionary, role type name --> role */
private final Map<String, Role> roleTypeNameDictionary = new HashMap<>();
/** the node runtime */
private transient NodeRuntime nodeRuntime;
/** Constructs a new Node instance. */
public Node() {
nodeTypeURI = null;
nodeTypeName = null;
nodeNickname = null;
nodeRuntime = null;
}
/** Constructs a new Node instance.
*
* @param nodeType the node
* @param nodeRuntime the node runtime
*/
public Node(
final NodeType nodeType,
final NodeRuntime nodeRuntime) {
//Preconditions
assert nodeType != null : "nodeType must not be null";
assert nodeType.getId() != null : "node type id must not be null";
assert nodeType.getTypeName() != null : "node type name must not be null";
this.nodeTypeURI = nodeType.getId();
this.nodeTypeName = nodeType.getTypeName();
this.nodeRuntime = nodeRuntime;
}
/** Gets the id assigned by the persistence framework.
*
* @return the id assigned by the persistence framework
*/
@Override
public URI getId() {
return id;
}
/** Gets the node type URI.
*
* @return the node type URI
*/
public URI getNodeTypeURI() {
return nodeTypeURI;
}
/** Returns whether this is the top friendship node.
*
* @return whether this is the top friendship node
*/
public boolean isTopFriendshipNode() {
return this.nodeTypeName.equals(AHCSConstants.TOP_FRIENDSHIP_NODE_TYPE);
}
/** Gets the node state value associated with the given variable name.
*
* @param stateVariableName the given variable name
* @return the state value associated with the given variable name
*/
public Object getNodeStateValue(final String stateVariableName) {
//Preconditions
assert stateVariableName != null : "stateVariableName must not be null";
assert !stateVariableName.isEmpty() : "stateVariableName must not be empty";
synchronized (stateVariableDictionary) {
if (stateVariableDictionary.isEmpty() && !stateValueBindings.isEmpty()) {
// lazy population of the state value dictionary from the persistent state value bindings
for (final StateValueBinding stateValueBinding : stateValueBindings) {
stateVariableDictionary.put(stateValueBinding.getVariableName(), stateValueBinding);
}
}
final StateValueBinding stateValueBinding = stateVariableDictionary.get(stateVariableName);
if (stateValueBinding == null) {
return null;
} else {
return stateValueBinding.getValue();
}
}
}
/** Sets the node state value associated with the given variable name.
*
* @param variableName the given variable name
* @param value the state value
*/
public void setNodeStateValue(final String variableName, final Object value) {
//Preconditions
assert variableName != null : "variableName must not be null";
assert !variableName.isEmpty() : "variableName must not be empty";
synchronized (stateVariableDictionary) {
if (stateVariableDictionary.isEmpty() && !stateValueBindings.isEmpty()) {
// lazy population of the state value dictionary from the persistent state value bindings
for (final StateValueBinding stateValueBinding : stateValueBindings) {
stateVariableDictionary.put(stateValueBinding.getVariableName(), stateValueBinding);
}
}
StateValueBinding stateValueBinding = stateVariableDictionary.get(variableName);
if (stateValueBinding == null) {
stateValueBinding = new StateValueBinding(variableName, value);
stateVariableDictionary.put(variableName, stateValueBinding);
stateValueBindings.add(stateValueBinding);
} else {
stateValueBinding.setValue(value);
}
}
}
/** Removes the node state value binding for the given variable.
*
* @param variableName the variable name
*/
public void removeNodeStateValueBinding(final String variableName) {
//Preconditions
assert variableName != null : "variableName must not be null";
assert !variableName.isEmpty() : "variableName must not be empty";
synchronized (stateVariableDictionary) {
if (stateVariableDictionary.isEmpty() && !stateValueBindings.isEmpty()) {
// lazy population of the state value dictionary from the persistent state value bindings
for (final StateValueBinding stateValueBinding : stateValueBindings) {
stateVariableDictionary.put(stateValueBinding.getVariableName(), stateValueBinding);
}
}
final StateValueBinding stateValueBinding = stateVariableDictionary.remove(variableName);
if (stateValueBinding != null) {
final boolean isRemoved = stateValueBindings.remove(stateValueBinding);
assert isRemoved;
}
}
}
/** Installs the roles for this node.
*
* @param nodeAccess the node access object
*/
public void installRoles(final NodeAccess nodeAccess) {
//Preconditions
assert nodeAccess != null : "nodeAccess must not be null";
if (roles.isEmpty()) {
// this node has not yet been persisted
final NodeType nodeType = nodeAccess.getNodeType(this);
final Collection<RoleType> roleTypes = nodeType.getAllRoleTypes();
LOGGER.info(nodeNickname + ": installing " + roleTypes.size() + " roles from role types");
synchronized (roles) {
for (final RoleType roleType : roleTypes) {
final Role role = new Role(
roleType,
nodeRuntime);
role.setNode(this);
nodeRuntime.setX509SecurityInfoAndIdForRole(role);
nodeAccess.getRDFEntityManager().persist(role);
LOGGER.info(nodeNickname + ": persisting role " + role);
roles.add(role);
installRole(role, nodeAccess);
}
}
} else {
// this node has already been persisted
LOGGER.info(nodeNickname + ": installing " + roles.size() + " roles");
synchronized (roles) {
for (final Role role : roles) {
installRole(role, nodeAccess);
}
}
}
}
/** Installs the given role.
*
* @param role the given role
* @param nodeAccess the node access object
*/
private void installRole(final Role role, final NodeAccess nodeAccess) {
//Preconditions
assert role != null : "role must not be null";
assert nodeAccess != null : "nodeAccess must not be null";
assert !roleIdDictionary.containsKey(role.getId()) : "role must not already be installed " + role;
roleIdDictionary.put(role.getId(), role);
final RoleType roleType = nodeAccess.getRoleType(role);
assert roleType != null : "role type not found for " + role;
assert !roleTypeNameDictionary.containsKey(roleType.getTypeName()) : "role type must not already be installed " + role;
roleTypeNameDictionary.put(roleType.getTypeName(), role);
nodeRuntime.registerRole(role);
role.installSkills(nodeAccess);
}
/** Returns an unmodifiable view of the set of roles.
*
* @return an unmodifiable view of the set of roles
*/
public Collection<Role> getRoles() {
synchronized (roleIdDictionary) {
return Collections.unmodifiableCollection(roleIdDictionary.values());
}
}
/** Gets the role for the given role type name or null if not found.
*
* @param roleTypeName the given role type name
* @return the role or null if not found
*/
public Role getRoleForTypeName(final String roleTypeName) {
//Preconditions
assert roleTypeName != null : "roleTypeName must not be null";
assert !roleTypeName.isEmpty() : "roleTypeName must not be empty";
return roleTypeNameDictionary.get(roleTypeName);
}
/** Returns a string representation of this object.
*
* @return a string representation of this object
*/
@Override
public String toString() {
if (nodeTypeName == null) {
return "[Node]";
} else {
return "[" + nodeNickname + ": " + nodeTypeName + "]";
}
}
/** Gets the node runtime.
*
* @return the node runtime
*/
public NodeRuntime getNodeRuntime() {
return nodeRuntime;
}
/** Sets the node runtime.
*
* @param nodeRuntime the node runtime
*/
public void setNodeRuntime(NodeRuntime nodeRuntime) {
this.nodeRuntime = nodeRuntime;
}
/** Gets the node type name.
*
* @return the node type name
*/
public String getNodeTypeName() {
return nodeTypeName;
}
/** Gets the node nickname.
*
* @return the node nickname
*/
public String getNodeNickname() {
return nodeNickname;
}
/** Sets the node nickname.
*
* @param nodeNickname the node nickname
*/
public void setNodeNickname(final String nodeNickname) {
this.nodeNickname = nodeNickname;
}
/** Returns whether some other object equals this one.
*
* @param obj the other object
* @return whether the other object equals this one
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Node other = (Node) obj;
if (Objects.equals(this.id, other.id)) {
assert (this.nodeNickname == null && other.nodeNickname == null) || (this.nodeNickname.equals(other.nodeNickname)) :
"nodes having equal ids must have equal nicknames: " + this + " " + other;
return true;
} else {
return false;
}
}
/** Returns whether some other object equals this one.
*
* @param obj the other object
* @return whether some other object equals this one
*/
/** Returns a hash code for this object.
*
* @return a hash code for this object
*/
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + Objects.hashCode(this.id);
hash = 29 * hash + Objects.hashCode(this.nodeTypeURI);
return hash;
}
/** Ensures that this persistent object is fully instantiated. */
@Override
public void instantiate() {
for (final StateValueBinding stateValueBinding : stateValueBindings) {
stateValueBinding.instantiate();
}
for (final Role role : roles) {
role.instantiate();
}
}
/** Recursively persists this RDF entity and all its components.
*
* @param rdfEntityManager the RDF entity manager
* @param overrideContext the user's belief context, or null to persist to each object's default context
*/
public void cascadePersist(
final RDFEntityManager rdfEntityManager,
final URI overrideContext) {
//Preconditions
assert rdfEntityManager != null : "rdfEntityManager must not be null";
cascadePersist(this, rdfEntityManager, overrideContext);
}
/** Recursively persists this RDF entity and all its components.
*
* @param rootRDFEntity the root RDF entity
* @param rdfEntityManager the RDF entity manager
* @param overrideContext the user's belief context, or null to persist to each object's default context
*/
@Override
public void cascadePersist(RDFPersistent rootRDFEntity, RDFEntityManager rdfEntityManager, URI overrideContext) {
//Preconditions
assert rdfEntityManager != null : "rdfEntityManager must not be null";
for (final StateValueBinding stateValueBinding : stateValueBindings) {
stateValueBinding.cascadePersist(
rootRDFEntity,
rdfEntityManager,
overrideContext);
}
for (final Role role : roles) {
role.cascadePersist(
rootRDFEntity,
rdfEntityManager,
overrideContext);
}
rdfEntityManager.persist(this, overrideContext);
}
/** Recursively removes this RDF entity and all its unshared components.
*
* @param rootRDFEntity the root RDF entity
* @param rdfEntityManager the RDF entity manager
*/
@Override
public void cascadeRemove(RDFPersistent rootRDFEntity, RDFEntityManager rdfEntityManager) {
//Preconditions
assert rdfEntityManager != null : "rdfEntityManager must not be null";
for (final StateValueBinding stateValueBinding : stateValueBindings) {
stateValueBinding.cascadeRemove(
rootRDFEntity,
rdfEntityManager);
}
for (final Role role : roles) {
role.cascadeRemove(
rootRDFEntity,
rdfEntityManager);
}
rdfEntityManager.remove(this);
}
/** Compares another node to this one.
*
* @param that the other node
* @return -1 if less than, 0 if equal, otherwise return +1
*/
@Override
public int compareTo(final Node that) {
//Preconditions
assert that != null : "that must not be null";
return this.nodeNickname.compareTo(that.nodeNickname);
}
}