/*
* Copyright 2014 Igor Maznitsa (http://www.igormaznitsa.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.igormaznitsa.prol.containers;
import com.igormaznitsa.prol.data.Term;
import com.igormaznitsa.prol.data.Operator;
import com.igormaznitsa.prol.data.TermStruct;
import com.igormaznitsa.prol.exceptions.ProlKnowledgeBaseException;
import com.igormaznitsa.prol.logic.ProlContext;
import com.igormaznitsa.prol.logic.triggers.ProlTriggerType;
import com.igormaznitsa.prol.utils.Utils;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantLock;
/**
* The class implements the knowledge base for a Prol engine
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
*/
public final class MemoryKnowledgeBase implements KnowledgeBase {
/**
* The variable contains the base identifier
*/
private final String basedId;
/**
* The table contains defined operators
*/
private final Map<String, OperatorContainer> operatorTable = new HashMap<String, OperatorContainer>();
/**
* The table contains defined predicates
*/
private final Map<String, KnowledgeBaseInsideClauseList> predicateTable = new HashMap<String, KnowledgeBaseInsideClauseList>();
/**
* The link to the context which is the owner of the knowledge base
*/
private final ProlContext context;
private final ReentrantLock operatorLocker = new ReentrantLock();
private final ReentrantLock predicateLocker = new ReentrantLock();
/**
* The constructor
*
* @param context the context-owner, must not be null
* @param baseId the identifier of the base, must not be null
*/
public MemoryKnowledgeBase(final ProlContext context, final String baseId) {
if (baseId == null || context == null) {
throw new IllegalArgumentException("One from the erguments is null");
}
this.basedId = baseId;
this.context = context;
}
/**
* Auxiliary constructor to make new knowledge base as a snapshot of existent
* knowledge base
*
* @param context the content for new knowledge base
* @param baseId the base id for new knowledge base
* @param etalon the etalon knowledge base
*/
private MemoryKnowledgeBase(final ProlContext context, final String baseId, final MemoryKnowledgeBase etalon) {
this.basedId = baseId;
this.context = context;
etalon.operatorLocker.lock();
try {
etalon.predicateLocker.lock();
try {
for (final Entry<String, OperatorContainer> item : etalon.operatorTable.entrySet()) {
operatorTable.put(item.getKey(), item.getValue().makeCopy());
}
for (final Entry<String, KnowledgeBaseInsideClauseList> item : etalon.predicateTable.entrySet()) {
predicateTable.put(item.getKey(), item.getValue().makeCopy());
}
}
finally {
etalon.predicateLocker.unlock();
}
}
finally {
etalon.operatorLocker.unlock();
}
}
@Override
public String getVocabularyId() {
return basedId;
}
@Override
public void addOperators(final Operator[] operators) {
operatorLocker.lock();
try {
for (int li = 0; li < operators.length; li++) {
addOperator(operators[li]);
}
}
finally {
operatorLocker.unlock();
}
}
@Override
public Operator getOperatorForTypeAndName(final String name, final int type) {
OperatorContainer opContainer = null;
operatorLocker.lock();
try {
opContainer = operatorTable.get(name);
}
finally {
operatorLocker.unlock();
}
Operator result = null;
if (opContainer != null) {
result = opContainer.getForTypePrecisely(type);
}
return result;
}
@Override
public boolean removeOperator(final String name, final int type) {
OperatorContainer opContainer;
operatorLocker.lock();
try {
opContainer = operatorTable.get(name);
}
finally {
operatorLocker.unlock();
}
boolean result = false;
if (opContainer != null) {
if (opContainer.isSystem()) {
throw new SecurityException("Attemption to remove a system operator");
}
else {
result = opContainer.removeOperatorForType(type);
}
}
return result;
}
@Override
public void addOperator(final Operator operator) {
final String operatorName = operator.getText();
final ReentrantLock lockerOp = operatorLocker;
lockerOp.lock();
try {
if (context.isSystemOperator(operator.getText())) {
throw new SecurityException("Attemption to override a system operator [" + operator.getText() + ']');
}
OperatorContainer list = operatorTable.get(operatorName);
if (list == null) {
list = new OperatorContainer(operator);
operatorTable.put(operatorName, list);
}
else {
if (!list.setOperator(operator)) {
throw new SecurityException("Such or a compatible operator is already presented [" + operatorName + ']');
}
}
}
finally {
lockerOp.unlock();
}
}
@Override
public OperatorContainer findOperatorForName(final String name) {
final OperatorContainer systemOperator = context.getSystemOperatorForName(name);
OperatorContainer result = null;
if (systemOperator == null) {
final ReentrantLock lockerOp = operatorLocker;
lockerOp.lock();
try {
result = operatorTable.get(name);
}
finally {
lockerOp.unlock();
}
}
else {
result = systemOperator;
}
return result;
}
@Override
public boolean hasOperatorStartsWith(final String str) {
boolean result = false;
if (context.hasSystemOperatorStartsWith(str)) {
result = true;
}
else {
final ReentrantLock lockerOp = operatorLocker;
lockerOp.lock();
try {
final Iterator<String> operators = operatorTable.keySet().iterator();
while (operators.hasNext()) {
if (operators.next().startsWith(str)) {
result = true;
break;
}
}
}
finally {
lockerOp.unlock();
}
}
return result;
}
@Override
public void write(final PrintWriter writer) {
if (writer == null) {
throw new IllegalArgumentException("Writer must not be null");
}
final ReentrantLock lockerOp = operatorLocker;
final ReentrantLock lockerPred = predicateLocker;
lockerOp.lock();
try {
// write operators
final Iterator<OperatorContainer> operators = operatorTable.values().iterator();
while (operators.hasNext()) {
final OperatorContainer container = operators.next();
container.write(writer);
}
writer.println();
}
finally {
lockerOp.unlock();
}
// write predicates
lockerPred.lock();
try {
final Iterator<KnowledgeBaseInsideClauseList> predicates = predicateTable.values().iterator();
while (predicates.hasNext()) {
final KnowledgeBaseInsideClauseList list = predicates.next();
list.write(writer);
}
}
finally {
lockerPred.unlock();
}
}
/**
* Assert new clause ino the knowledge base
*
* @param clause the clause to be added
* @param asFirst if true then the clause will be made as the first in the
* list of similar clauses, else as the last
* @return true if the clause has been added successfully, else false
* @throws com.igormaznitsa.prol.exceptions.ProlKnowledgeBaseException if such
* clause is incompatible with the knowledge base
*/
private boolean assertClause(final TermStruct clause, final boolean asFirst) {
try {
final String uid;
if (clause.isFunctorLikeRuleDefinition()) {
// it's clause
// get left part
Term leftPart = clause.getElement(0);
if (leftPart.getTermType() == Term.TYPE_ATOM) {
leftPart = new TermStruct(leftPart);
clause.setElement(0, leftPart);
}
uid = leftPart.getSignature();
}
else {
// it's just structure
uid = clause.getSignature();
}
boolean result = false;
final ReentrantLock lockerPred = predicateLocker;
lockerPred.lock();
try {
KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
if (list == null) {
// it's new
list = new KnowledgeBaseInsideClauseList();
predicateTable.put(uid, list);
}
result = asFirst ? list.asserta(clause) : list.assertz(clause);
}
finally {
lockerPred.unlock();
}
// notify triggers if they are presented
if (result && context.hasRegisteredTriggersForSignature(uid, ProlTriggerType.TRIGGER_ASSERT)) {
context.notifyTriggersForSignature(uid, ProlTriggerType.TRIGGER_ASSERT);
}
return result;
}
catch (IllegalArgumentException ex) {
throw new ProlKnowledgeBaseException("You can't add such atom into the base [" + clause.getSourceLikeRepresentation() + ']', ex);
}
}
@Override
public FactIterator getFactIterator(final TermStruct template) {
final String uid = template.getSignature();
final ReentrantLock lockerPred = predicateLocker;
lockerPred.lock();
try {
final KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
FactIterator result = null;
if (list != null) {
result = new MemoryFactIterator(list, template);
}
return result;
}
finally {
lockerPred.unlock();
}
}
@Override
public RuleIterator getRuleIterator(final TermStruct template) {
final String uid = template.getSignature();
final ReentrantLock lockerPred = predicateLocker;
lockerPred.lock();
try {
final KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
RuleIterator result = null;
if (list != null) {
result = new MemoryRuleIterator(list, template);
}
return result;
}
finally {
lockerPred.unlock();
}
}
@Override
public ClauseIterator getClauseIterator(final TermStruct template) {
final String uid = template.getSignature();
final ReentrantLock lockerPred = predicateLocker;
lockerPred.lock();
try {
final KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
ClauseIterator result = null;
if (list != null) {
result = new MemoryClauseIterator(list, template);
}
return result;
}
finally {
lockerPred.unlock();
}
}
@Override
public boolean assertZ(final TermStruct clause) {
return assertClause(clause, false);
}
@Override
public boolean assertA(final TermStruct clause) {
return assertClause(clause, true);
}
@Override
public boolean retractAll(final TermStruct clause) {
TermStruct struct = clause;
if (struct.isFunctorLikeRuleDefinition()) {
// it's a clause
struct = (TermStruct) struct.getElement(0);
}
final ReentrantLock lockerPred = predicateLocker;
boolean result = false;
String uid = null;
lockerPred.lock();
try {
uid = struct.getSignature();
final KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
if (list != null) {
result = list.retractall(struct) != 0;
if (result && list.size() == 0) {
// delete from base
predicateTable.remove(uid);
}
}
}
finally {
lockerPred.unlock();
}
// notify triggers if they are presented
if (result && context.hasRegisteredTriggersForSignature(uid, ProlTriggerType.TRIGGER_RETRACT)) {
context.notifyTriggersForSignature(uid, ProlTriggerType.TRIGGER_RETRACT);
}
return result;
}
@Override
public boolean retractA(final TermStruct clause) {
TermStruct struct = clause;
if (struct.isFunctorLikeRuleDefinition()) {
// it's a clause
struct = (TermStruct) struct.getElement(0);
}
final ReentrantLock lockerPred = predicateLocker;
boolean result = false;
String uid = null;
lockerPred.lock();
try {
uid = struct.getSignature();
final KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
if (list != null) {
result = list.retracta(struct);
if (result && list.size() == 0) {
// delete from base
predicateTable.remove(uid);
}
}
}
finally {
lockerPred.unlock();
}
// notify triggers if they are presented
if (result && context.hasRegisteredTriggersForSignature(uid, ProlTriggerType.TRIGGER_RETRACT)) {
context.notifyTriggersForSignature(uid, ProlTriggerType.TRIGGER_RETRACT);
}
return result;
}
@Override
public boolean retractZ(final TermStruct clause) {
TermStruct struct = clause;
if (struct.isFunctorLikeRuleDefinition()) {
// it's a clause
struct = (TermStruct) struct.getElement(0);
}
boolean result = false;
String uid = null;
final ReentrantLock lockerPred = predicateLocker;
lockerPred.lock();
try {
uid = struct.getSignature();
final KnowledgeBaseInsideClauseList list = predicateTable.get(uid);
if (list != null) {
result = list.retractz(struct);
if (result && list.size() == 0) {
// delete from base
predicateTable.remove(uid);
}
}
}
finally {
lockerPred.unlock();
}
// notify triggers if they are presented
if (result && context.hasRegisteredTriggersForSignature(uid, ProlTriggerType.TRIGGER_RETRACT)) {
context.notifyTriggersForSignature(uid, ProlTriggerType.TRIGGER_RETRACT);
}
return result;
}
@Override
public void abolish(final String signature) {
final ReentrantLock lockerPred = predicateLocker;
boolean result = false;
final String normalizedUID = Utils.normalizeSignature(signature);
if (normalizedUID == null) {
throw new IllegalArgumentException("Wrong signature format \'" + signature + '\'');
}
lockerPred.lock();
try {
result = predicateTable.remove(normalizedUID) != null;
}
finally {
lockerPred.unlock();
}
// notify triggers if they are presented
if (result && context.hasRegisteredTriggersForSignature(normalizedUID, ProlTriggerType.TRIGGER_RETRACT)) {
context.notifyTriggersForSignature(normalizedUID, ProlTriggerType.TRIGGER_RETRACT);
}
}
@Override
public Iterator<OperatorContainer> getOperatorIterator() {
final ReentrantLock lockerOp = operatorLocker;
lockerOp.lock();
try {
return operatorTable.values().iterator();
}
finally {
lockerOp.unlock();
}
}
@Override
public KnowledgeBase makeCopy(final ProlContext context) {
return new MemoryKnowledgeBase(context, basedId + "_copy", this);
}
}