/*
* 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.libraries;
import com.igormaznitsa.prol.annotations.Determined;
import com.igormaznitsa.prol.annotations.Evaluable;
import com.igormaznitsa.prol.annotations.ItChangesGoalChain;
import com.igormaznitsa.prol.data.Term;
import com.igormaznitsa.prol.data.TermStruct;
import com.igormaznitsa.prol.exceptions.ProlAbstractCatcheableException;
import com.igormaznitsa.prol.exceptions.ProlCriticalError;
import com.igormaznitsa.prol.exceptions.ProlEvaluationErrorException;
import com.igormaznitsa.prol.exceptions.ProlException;
import com.igormaznitsa.prol.exceptions.ProlInstantiationErrorException;
import com.igormaznitsa.prol.logic.Goal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* The auxulary inside class describes a predicate processor. It means that the
* processor contains all links to method and the object instance of a library
* which should execute the predicate.
*
* @author Igor Maznitsa (igor.maznitsa@igormaznitsa.com)
* @see java.lang.reflect.Method
* @see com.igormaznitsa.prol.libraries.ProlAbstractLibrary
* @see com.igormaznitsa.prol.libraries.PredicateTemplate
*/
public final class PredicateProcessor {
/**
* The variable contains the predicate signature for the predicate which is
* being lnked with the processor
*/
private final String predicateSignature;
/**
* The variable contains the method which will be called to process the
* predicate
*/
private final Method method;
/**
* The variable contains the owner library which contains the method linked
* with this processor
*/
private final ProlAbstractLibrary ownerLibrary;
/**
* If the flag is false, then the method linked with the processor will not
* return any result
*/
private final boolean voidResult;
/**
* if the flag is true then the linked predicate is a determined one and it
* will not place cjhoice points
*/
private final boolean determined;
/**
* if the flag is true then the linked predicate is an evaluable arithmetic
* predcicate
*/
private final boolean evaluable;
/**
* if the flag is true then the linked predicate changes goal chain if it's
* true
*/
private final boolean changesGoalChain;
/**
* The array contains templates which should be checked (if they are
* presented) before processor executing to check its arguments
*/
private final PredicateTemplate[][] templates;
//---inside constants for classes -------
private static final Class<?> CLASS_RESULT_VOID = void.class;
private static final Class<Determined> CLASS_ANNOTATION_DETERMINED = Determined.class;
private static final Class<Evaluable> CLASS_ANNOTATION_EVALUABLE = Evaluable.class;
private static final Class<ItChangesGoalChain> CLASS_CHANGE_GOAL_CHAIN = ItChangesGoalChain.class;
//---------------------------------------
/**
* The constant describes a NULL_PROCESSOR which does nothing
*/
public static final PredicateProcessor NULL_PROCESSOR = new PredicateProcessor(null, null, null, null);
/**
* The constructor
*
* @param owner the owner library instance for the processor, must not be null
* @param signature the signature of the predicate which is linked with the
* processor
* @param method the method which will be called when the processor is
* executed
* @param templates the templates which should be checked with a predicate
* arguments before the processor execution, can be null
*/
protected PredicateProcessor(final ProlAbstractLibrary owner, final String signature, final Method method, final PredicateTemplate[][] templates) {
super();
this.predicateSignature = signature;
this.ownerLibrary = owner;
this.method = method;
this.templates = templates;
if (method == null) {
voidResult = true;
determined = true;
evaluable = false;
changesGoalChain = false;
}
else {
voidResult = method.getReturnType() == CLASS_RESULT_VOID;
determined = method.isAnnotationPresent(CLASS_ANNOTATION_DETERMINED);
evaluable = method.isAnnotationPresent(CLASS_ANNOTATION_EVALUABLE);
changesGoalChain = method.isAnnotationPresent(CLASS_CHANGE_GOAL_CHAIN);
}
}
public final boolean doesChangeGoalChain() {
return changesGoalChain;
}
/**
* Get the determined flag
*
* @return true if the predicate has been marked as a determined one and it
* make not any choice point
*/
public final boolean isDetermined() {
return determined;
}
/**
* Get the evaluable flag
*
* @return true if the predicate can be calculated as an arithmetic predicate
*/
public final boolean isEvaluable() {
return evaluable;
}
/**
* Get the owner library for the processor
*
* @return the owner library for the processor
*/
public final ProlAbstractLibrary getLibrary() {
return ownerLibrary;
}
/**
* Get the signature of the linked predicate
*
* @return the signature of the linked predicate
*/
public final String getSignature() {
return predicateSignature;
}
/**
* Get the method which will be called during the processor execution
*
* @return the method which implements needed functionality
*/
public final Method getMethod() {
return method;
}
/**
* Get the templates which should be checked with predicate argument before
* execution of the processor
*
* @return the templates as an array but it can be null if we don't have any
* template
*/
public PredicateTemplate[][] getTemplates() {
return templates;
}
/**
* Check a structure for compatibility with a template of the processor
*
* @param predicate the predicate to be checked, must not be null
* @return mainly null, but if there is elements of a template which should
* not be changed during processing, they will be returned as the array
*/
private Term[] checkTemplates(final TermStruct predicate) {
final PredicateTemplate[][] templatesarray = this.templates;
final int len = templatesarray.length;
final Term[] structelements = predicate.getElementsAsArray();
final int structElementsNumber = predicate.getArity();
ProlInstantiationErrorException lastException = null;
Term[] result = null;
for (int li = 0; li < len; li++) {
final PredicateTemplate[] curtemplate = templatesarray[li];
final int lencur = curtemplate.length;
lastException = null;
Term[] currentResult = null;
try {
if (lencur == structElementsNumber) {
for (int ld = 0; ld < lencur; ld++) {
final PredicateTemplate curTemplate = curtemplate[ld];
final Term element = structelements[ld];
if (curTemplate.check(element)) {
if (currentResult == null) {
currentResult = new Term[lencur];
}
currentResult[ld] = element;
}
}
}
}
catch (ProlInstantiationErrorException ex) {
lastException = ex;
}
if (lastException == null) {
result = currentResult;
break;
}
}
if (lastException != null) {
throw lastException;
}
return result;
}
/**
* Execute an evaluable predicate linked with the processor
*
* @param goal the goal which contain the predicate, must not be null
* @param predicate the predicate which linked with the processor, must not be
* null
* @return the term as the result of the processing
*/
public final Term executeEvaluable(final Goal goal, final TermStruct predicate) {
try {
Term[] nonchangeable = null;
if (templates != null) {
nonchangeable = checkTemplates(predicate);
}
final Object result = method.invoke(ownerLibrary, goal, predicate);
if (nonchangeable != null) {
final Term[] elements = predicate.getElementsAsArray();
final int len = elements.length;
for (int li = 0; li < len; li++) {
if (nonchangeable[li] == null) {
continue;
}
if (nonchangeable[li].hasAnyDifference(elements[li])) {
throw new ProlInstantiationErrorException("Nonchangeable element was changed [" + nonchangeable[li] + "<>" + elements[li] + "]", predicate);
}
}
}
return (Term) result;
}
catch (IllegalAccessException ex) {
throw new ProlCriticalError("Illegal access exception at " + predicate, ex);
}
catch (InvocationTargetException thr) {
thr.printStackTrace();
final Throwable cause = thr.getCause();
if (cause instanceof ArithmeticException) {
throw new ProlEvaluationErrorException(cause.getMessage(), predicate);
}
if (cause instanceof ProlAbstractCatcheableException) {
throw (ProlAbstractCatcheableException) cause;
}
if (cause instanceof ProlException) {
throw (ProlException) cause;
}
throw new ProlCriticalError("Exception at [" + goal + ']', cause);
}
}
/**
* Execute a predicate linked with the processor
*
* @param goal the goal which contain the predicate, must not be null
* @param predicate the predicate which linked with the processor, must not be
* null
* @return the result of the predicate processing, true if all ok and false if
* fail
* @throws InterruptedException it will be thrown if the thread has been
* interrupted
*/
public final boolean execute(final Goal goal, final TermStruct predicate) throws InterruptedException {
try {
Term[] nonchangeable = null;
if (templates != null) {
nonchangeable = checkTemplates(predicate);
}
final Object result = method.invoke(ownerLibrary, goal, predicate);
if (nonchangeable != null) {
final Term[] elements = predicate.getElementsAsArray();
final int len = elements.length;
for (int li = 0; li < len; li++) {
if (nonchangeable[li] == null) {
continue;
}
if (nonchangeable[li].hasAnyDifference(elements[li])) {
throw new ProlInstantiationErrorException("Nonchangeable element was changed [" + nonchangeable[li] + "<>" + elements[li] + "]", predicate);
}
}
}
if (voidResult) {
return true;
}
else if (result instanceof Boolean) {
return (Boolean) result;
}
else {
return result != null;
}
}
catch (IllegalAccessException ex) {
throw new ProlException("Illegal access exception at " + predicate, ex);
}
catch (Exception thr) {
if (thr instanceof ProlException) {
throw (ProlException) thr;
}
final Throwable cause = thr.getCause();
if (cause instanceof ThreadDeath) {
throw (ThreadDeath) cause;
}
if (cause instanceof InterruptedException) {
throw (InterruptedException) cause;
}
if (cause instanceof ProlAbstractCatcheableException) {
throw (ProlAbstractCatcheableException) cause;
}
if (cause instanceof ProlException) {
throw (ProlException) cause;
}
throw new ProlCriticalError("Exception at [" + goal + ']', cause);
}
}
@Override
public String toString() {
return "Predicate processor " + predicateSignature + ' ' + method;
}
}