/*
* Copyright 2010 JBoss Inc
*
* 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 br.ufes.inf.lprm.scene.base.evaluators;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.drools.RuntimeDroolsException;
import org.drools.base.BaseEvaluator;
import org.drools.base.ValueType;
import org.drools.base.evaluators.EvaluatorDefinition;
import org.drools.base.evaluators.Operator;
import org.drools.base.evaluators.TimeIntervalParser;
import org.drools.common.EventFactHandle;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalWorkingMemory;
import org.drools.rule.VariableRestriction.LongVariableContextEntry;
import org.drools.rule.VariableRestriction.ObjectVariableContextEntry;
import org.drools.rule.VariableRestriction.VariableContextEntry;
import org.drools.runtime.rule.FactHandle;
import org.drools.spi.Evaluator;
import org.drools.spi.FieldValue;
import org.drools.spi.InternalReadAccessor;
import org.drools.time.Interval;
import br.ufes.inf.lprm.situation.SituationType;
/**
* <p>The implementation of the 'before' evaluator definition.</p>
*
* <p>The <b><code>before</code></b> evaluator correlates two events and matches when the temporal
* distance from the event being correlated to the current correlated belongs to the distance range declared
* for the operator.</p>
*
* <p>Lets look at an example:</p>
*
* <pre>$eventA : EventA( this before[ 3m30s, 4m ] $eventB )</pre>
*
* <p>The previous pattern will match if and only if the temporal distance between the
* time when $eventA finished and the time when $eventB started is between ( 3 minutes
* and 30 seconds ) and ( 4 minutes ). In other words:</p>
*
* <pre> 3m30s <= $eventB.startTimestamp - $eventA.endTimeStamp <= 4m </pre>
*
* <p>The temporal distance interval for the <b><code>before</code></b> operator is optional:</p>
*
* <ul><li>If two values are defined (like in the example below), the interval starts on the
* first value and finishes on the second.</li>
* <li>If only one value is defined, then the interval starts on the value and finishes on
* the positive infinity.</li>
* <li>If no value is defined, it is assumed that the initial value is 1ms and the final value
* is the positive infinity.</li></ul>
*
* <p><b>NOTE:</b> it is allowed to define negative distances for this operator. Example:</p>
*
* <pre>$eventA : EventA( this before[ -3m30s, -2m ] $eventB )</pre>
*
* <p><b>NOTE:</b> if the initial value is greater than the finish value, the engine automatically
* reverse them, as there is no reason to have the initial value greater than the finish value. Example:
* the following two patterns are considered to have the same semantics:</p>
*
* <pre>
* $eventA : EventA( this before[ -3m30s, -2m ] $eventB )
* $eventA : EventA( this before[ -2m, -3m30s ] $eventB )
* </pre>
*/
public class BeforeEvaluatorDefinition
implements
EvaluatorDefinition {
public static final Operator BEFORE = Operator.addOperatorToRegistry( "before",
false );
public static final Operator NOT_BEFORE = Operator.addOperatorToRegistry( "before",
true );
private static final String[] SUPPORTED_IDS = {BEFORE.getOperatorString()};
private Map<String, BeforeEvaluator> cache = Collections.emptyMap();
private volatile TimeIntervalParser parser = new TimeIntervalParser();
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
cache = (Map<String, BeforeEvaluator>) in.readObject();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject( cache );
}
/**
* @inheridDoc
*/
public Evaluator getEvaluator(ValueType type,
Operator operator) {
return this.getEvaluator( type,
operator.getOperatorString(),
operator.isNegated(),
null );
}
/**
* @inheridDoc
*/
public Evaluator getEvaluator(ValueType type,
Operator operator,
String parameterText) {
return this.getEvaluator( type,
operator.getOperatorString(),
operator.isNegated(),
parameterText );
}
/**
* @inheritDoc
*/
public Evaluator getEvaluator(final ValueType type,
final String operatorId,
final boolean isNegated,
final String parameterText) {
return this.getEvaluator( type,
operatorId,
isNegated,
parameterText,
Target.HANDLE,
Target.HANDLE );
}
/**
* @inheritDoc
*/
public Evaluator getEvaluator(final ValueType type,
final String operatorId,
final boolean isNegated,
final String parameterText,
final Target left,
final Target right) {
if ( this.cache == Collections.EMPTY_MAP ) {
this.cache = new HashMap<String, BeforeEvaluator>();
}
String key = left+":"+right+":"+isNegated + ":" + parameterText;
BeforeEvaluator eval = this.cache.get( key );
if ( eval == null ) {
Long[] params = parser.parse( parameterText );
eval = new BeforeEvaluator( type,
isNegated,
params,
parameterText,
left == Target.FACT,
right == Target.FACT );
this.cache.put( key,
eval );
}
return eval;
}
/**
* @inheritDoc
*/
public String[] getEvaluatorIds() {
return SUPPORTED_IDS;
}
/**
* @inheritDoc
*/
public boolean isNegatable() {
return true;
}
/**
* @inheritDoc
*/
public Target getTarget() {
return Target.BOTH;
}
/**
* @inheritDoc
*/
public boolean supportsType(ValueType type) {
// supports all types, since it operates over fact handles
// Note: should we change this interface to allow checking of event classes only?
return true;
}
/**
* Implements the 'before' evaluator itself
*/
public static class BeforeEvaluator extends BaseEvaluator {
private static final long serialVersionUID = 510l;
private long initRange;
private long finalRange;
private String paramText;
private boolean unwrapLeft;
private boolean unwrapRight;
public BeforeEvaluator() {
}
public BeforeEvaluator(final ValueType type,
final boolean isNegated,
final Long[] parameters,
final String paramText,
final boolean unwrapLeft,
final boolean unwrapRight) {
super( type,
isNegated ? NOT_BEFORE : BEFORE );
this.paramText = paramText;
this.unwrapLeft = unwrapLeft;
this.unwrapRight = unwrapRight;
this.setParameters( parameters );
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
super.readExternal( in );
initRange = in.readLong();
finalRange = in.readLong();
paramText = (String) in.readObject();
unwrapLeft = in.readBoolean();
unwrapRight = in.readBoolean();
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeLong( initRange );
out.writeLong( finalRange );
out.writeObject( paramText );
out.writeBoolean( unwrapLeft );
out.writeBoolean( unwrapRight );
}
@Override
public Object prepareLeftObject(InternalFactHandle handle) {
return unwrapLeft ? handle.getObject() : handle;
}
@Override
public Object prepareRightObject(InternalFactHandle handle) {
return unwrapRight ? handle.getObject() : handle;
}
@Override
public boolean isTemporal() {
return true;
}
@Override
public Interval getInterval() {
long init = (this.finalRange == Interval.MAX) ? Interval.MIN : -this.finalRange;
long end = (this.initRange == Interval.MIN) ? Interval.MAX : -this.initRange;
if ( this.getOperator().isNegated() ) {
if ( init == Interval.MIN && end != Interval.MAX ) {
init = finalRange + 1;
end = Interval.MAX;
} else if ( init != Interval.MIN && end == Interval.MAX ) {
init = Interval.MIN;
end = initRange - 1;
} else if ( init == Interval.MIN && end == Interval.MAX ) {
init = 0;
end = -1;
} else {
init = Interval.MIN;
end = Interval.MAX;
}
}
return new Interval( init,
end );
}
public boolean evaluate(InternalWorkingMemory workingMemory,
final InternalReadAccessor extractor,
final Object object1,
final FieldValue object2) {
throw new RuntimeDroolsException( "The 'before' operator can only be used to compare one event to another, and never to compare to literal constraints." );
}
public boolean evaluateCachedRight(InternalWorkingMemory workingMemory,
final VariableContextEntry context,
final Object left) {
if ( context.rightNull ) {
return false;
}
long rightTS;
if ( this.unwrapRight ) {
if ( context instanceof ObjectVariableContextEntry ) {
if ( ((ObjectVariableContextEntry) context).right instanceof Date ) {
rightTS = ((Date) ((ObjectVariableContextEntry) context).right).getTime();
} else {
rightTS = ((Number) ((ObjectVariableContextEntry) context).right).longValue();
}
} else {
rightTS = ((LongVariableContextEntry) context).right;
}
} else {
Object rightFact = workingMemory.getObject((FactHandle) ((ObjectVariableContextEntry) context).right);
if(rightFact instanceof SituationType )
{
if ( ! ((SituationType) rightFact).isActive() ) {
rightTS = ((SituationType) rightFact).getDeactivation().getTimestamp();
}
else {
return false;
}
} else {
rightTS = ((EventFactHandle) ((ObjectVariableContextEntry) context).right).getEndTimestamp();
}
}
long leftTS;
if (this.unwrapLeft) {
leftTS = context.declaration.getExtractor().getLongValue( workingMemory,left );
}
else {
Object leftFact = workingMemory.getObject((FactHandle) left);
if(leftFact instanceof SituationType )
{
leftTS = ((SituationType) leftFact).getActivation().getTimestamp();
} else {
leftTS = ((EventFactHandle) left).getStartTimestamp();
}
}
long dist = leftTS - rightTS;
return this.getOperator().isNegated() ^ (dist >= this.initRange && dist <= this.finalRange);
}
public boolean evaluateCachedLeft(InternalWorkingMemory workingMemory,
final VariableContextEntry context,
final Object right) {
if ( context.extractor.isNullValue( workingMemory,
right ) ) {
return false;
}
long rightTS;
if (this.unwrapRight) {
rightTS = context.declaration.getExtractor().getLongValue( workingMemory,right );
}
else {
Object rightFact = workingMemory.getObject((FactHandle) right);
if(rightFact instanceof SituationType )
{
if ( ! ((SituationType) rightFact).isActive() ) {
rightTS = ((SituationType) rightFact).getDeactivation().getTimestamp();
}
else {
return false;
}
} else {
rightTS = ((EventFactHandle) right).getEndTimestamp();
}
}
long leftTS;
if ( this.unwrapLeft ) {
if ( context instanceof ObjectVariableContextEntry ) {
if ( ((ObjectVariableContextEntry) context).left instanceof Date ) {
leftTS = ((Date) ((ObjectVariableContextEntry) context).left).getTime();
} else {
leftTS = ((Number) ((ObjectVariableContextEntry) context).left).longValue();
}
} else {
leftTS = ((LongVariableContextEntry) context).left;
}
} else {
Object leftFact = workingMemory.getObject((FactHandle) ((ObjectVariableContextEntry) context).left);
if(leftFact instanceof SituationType )
{
leftTS = ((SituationType) leftFact).getActivation().getTimestamp();
} else {
leftTS = ((EventFactHandle) ((ObjectVariableContextEntry) context).left).getStartTimestamp();
}
}
long dist = leftTS - rightTS;
return this.getOperator().isNegated() ^ (dist >= this.initRange && dist <= this.finalRange);
}
public boolean evaluate(InternalWorkingMemory workingMemory,
final InternalReadAccessor extractor1,
final Object object1,
final InternalReadAccessor extractor2,
final Object object2) {
if ( extractor1.isNullValue( workingMemory,
object1 ) ) {
return false;
}
long rightTS;
if (this.unwrapRight) {
rightTS = extractor1.getLongValue( workingMemory, object1 );
}
else {
Object rightFact = workingMemory.getObject((FactHandle) object1);
if(rightFact instanceof SituationType ) {
if ( ! ((SituationType) rightFact).isActive() ) {
rightTS = ((SituationType) rightFact).getDeactivation().getTimestamp();
}
else {
return false;
}
} else {
rightTS = ((EventFactHandle) object1).getEndTimestamp();
}
}
long leftTS;
if (this.unwrapLeft) {
leftTS = extractor2.getLongValue( workingMemory, object2 );
}
else {
Object leftFact = workingMemory.getObject((FactHandle) object2);
if(leftFact instanceof SituationType ) {
leftTS = ((SituationType) leftFact).getActivation().getTimestamp();
} else {
leftTS = ((EventFactHandle) object2).getStartTimestamp();
}
}
long dist = leftTS - rightTS;
return this.getOperator().isNegated() ^ (dist >= this.initRange && dist <= this.finalRange);
}
public String toString() {
return this.getOperator().toString() + "[" + paramText + "]";
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int PRIME = 31;
int result = super.hashCode();
result = PRIME * result + (int) (finalRange ^ (finalRange >>> 32));
result = PRIME * result + (int) (initRange ^ (initRange >>> 32));
result = PRIME * result + ((paramText == null) ? 0 : paramText.hashCode());
result = PRIME * result + (unwrapLeft ? 1231 : 1237);
result = PRIME * result + (unwrapRight ? 1231 : 1237);
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if ( this == obj ) return true;
if ( !super.equals( obj ) ) return false;
if ( getClass() != obj.getClass() ) return false;
BeforeEvaluator other = (BeforeEvaluator) obj;
if ( finalRange != other.finalRange ) return false;
if ( initRange != other.initRange ) return false;
if ( paramText == null ) {
if ( other.paramText != null ) return false;
} else if ( !paramText.equals( other.paramText ) ) return false;
if ( unwrapLeft != other.unwrapLeft ) return false;
if ( unwrapRight != other.unwrapRight ) return false;
return true;
}
/**
* This methods sets the parameters appropriately.
*
* @param parameters
*/
private void setParameters(Long[] parameters) {
if ( parameters == null || parameters.length == 0 ) {
// open bounded range
this.initRange = 1;
this.finalRange = Long.MAX_VALUE;
return;
} else if ( parameters.length == 1 ) {
this.initRange = parameters[0].longValue();
this.finalRange = Long.MAX_VALUE;
} else if ( parameters.length == 2 ) {
if ( parameters[0].longValue() <= parameters[1].longValue() ) {
this.initRange = parameters[0].longValue();
this.finalRange = parameters[1].longValue();
} else {
this.initRange = parameters[1].longValue();
this.finalRange = parameters[0].longValue();
}
} else {
throw new RuntimeDroolsException( "[Before Evaluator]: Not possible to have more than 2 parameters: '" + paramText + "'" );
}
}
}
}