Package org.drools.rule.constraint

Source Code of org.drools.rule.constraint.MvelConstraint$PlainIndexEvaluator

package org.drools.rule.constraint;

import org.drools.base.DroolsQuery;
import org.drools.base.extractors.ArrayElementReader;
import org.drools.base.mvel.MVELCompilationUnit;
import org.drools.common.InternalFactHandle;
import org.drools.common.InternalRuleBase;
import org.drools.common.InternalWorkingMemory;
import org.drools.core.util.AbstractHashTable.FieldIndex;
import org.drools.core.util.BitMaskUtil;
import org.drools.core.util.MemoryUtil;
import org.drools.core.util.index.IndexUtil;
import org.drools.reteoo.LeftTuple;
import org.drools.rule.ContextEntry;
import org.drools.rule.Declaration;
import org.drools.rule.IndexEvaluator;
import org.drools.rule.IndexableConstraint;
import org.drools.rule.MVELDialectRuntimeData;
import org.drools.rule.MutableTypeConstraint;
import org.drools.rule.constraint.ConditionAnalyzer.CombinedCondition;
import org.drools.rule.constraint.ConditionAnalyzer.Condition;
import org.drools.rule.constraint.ConditionAnalyzer.EvaluatedExpression;
import org.drools.rule.constraint.ConditionAnalyzer.Expression;
import org.drools.rule.constraint.ConditionAnalyzer.FieldAccessInvocation;
import org.drools.rule.constraint.ConditionAnalyzer.Invocation;
import org.drools.rule.constraint.ConditionAnalyzer.MethodInvocation;
import org.drools.rule.constraint.ConditionAnalyzer.SingleCondition;
import org.drools.spi.AcceptsReadAccessor;
import org.drools.spi.FieldValue;
import org.drools.spi.InternalReadAccessor;
import org.kie.concurrent.ExecutorProviderFactory;
import org.kie.internal.utils.CompositeClassLoader;
import org.kie.runtime.rule.Variable;
import org.mvel2.ParserConfiguration;
import org.mvel2.compiler.CompiledExpression;
import org.mvel2.compiler.ExecutableStatement;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

import static org.drools.core.util.ClassUtils.getter2property;
import static org.drools.core.util.StringUtils.extractFirstIdentifier;
import static org.drools.core.util.StringUtils.skipBlanks;

public class MvelConstraint extends MutableTypeConstraint implements IndexableConstraint, AcceptsReadAccessor {
    protected static final boolean TEST_JITTING = false;
    protected static final int JIT_THRESOLD = 20; // Integer.MAX_VALUE;

    protected final transient AtomicInteger invocationCounter = new AtomicInteger(1);
    protected transient boolean jitted = false;

    private String packageName;
    protected String expression;
    private IndexUtil.ConstraintType constraintType = IndexUtil.ConstraintType.UNKNOWN;
    private Declaration[] declarations;
    private Declaration indexingDeclaration;
    private InternalReadAccessor extractor;
    private boolean isUnification;
    protected boolean isDynamic;
    private FieldValue fieldValue;

    protected MVELCompilationUnit compilationUnit;

    protected transient volatile ConditionEvaluator conditionEvaluator;
    private transient volatile Condition analyzedCondition;

    public MvelConstraint() {}

    public MvelConstraint(String packageName,
                          String expression,
                          MVELCompilationUnit compilationUnit,
                          IndexUtil.ConstraintType constraintType,
                          FieldValue fieldValue,
                          InternalReadAccessor extractor) {
        this.packageName = packageName;
        this.expression = expression;
        this.compilationUnit = compilationUnit;
        this.constraintType = constraintType;
        this.declarations = new Declaration[0];
        this.fieldValue = fieldValue;
        this.extractor = extractor;
    }

    public MvelConstraint(String packageName,
                          String expression,
                          Declaration[] declarations,
                          MVELCompilationUnit compilationUnit,
                          boolean isDynamic) {
        this.packageName = packageName;
        this.expression = expression;
        this.declarations = declarations;
        this.compilationUnit = compilationUnit;
        this.isDynamic = isDynamic;
    }

    public MvelConstraint(String packageName,
                          String expression,
                          Declaration[] declarations,
                          MVELCompilationUnit compilationUnit,
                          IndexUtil.ConstraintType constraintType,
                          Declaration indexingDeclaration,
                          InternalReadAccessor extractor,
                          boolean isUnification) {
        this.packageName = packageName;
        this.expression = expression;
        this.compilationUnit = compilationUnit;
        this.constraintType = indexingDeclaration != null ? constraintType : IndexUtil.ConstraintType.UNKNOWN;
        this.declarations = declarations == null ? new Declaration[0] : declarations;
        this.indexingDeclaration = indexingDeclaration;
        this.extractor = extractor;
        this.isUnification = isUnification;
    }

    public void setReadAccessor(InternalReadAccessor readAccessor) {
        this.extractor = readAccessor;
    }

    public String getPackageName() {
        return packageName;
    }

    public String getExpression() {
        return expression;
    }

    public boolean isUnification() {
        return isUnification;
    }

    public void unsetUnification() {
        isUnification = false;
    }

    public boolean isIndexable(short nodeType) {
        return getConstraintType().isIndexableForNode(nodeType);
    }

    public IndexUtil.ConstraintType getConstraintType() {
        return constraintType;
    }

    public FieldValue getField() {
        return fieldValue;
    }

    public boolean isAllowed(InternalFactHandle handle, InternalWorkingMemory workingMemory, ContextEntry context) {
        if (isUnification) {
            throw new UnsupportedOperationException( "Should not be called" );
        }

        return evaluate(handle.getObject(), workingMemory, null);
    }

    public boolean isAllowedCachedLeft(ContextEntry context, InternalFactHandle handle) {
        if (isUnification) {
            if (((UnificationContextEntry)context).getVariable() != null) {
                return true;
            }
            context = ((UnificationContextEntry)context).getContextEntry();
        }

        MvelContextEntry mvelContextEntry = (MvelContextEntry)context;
        return evaluate(handle.getObject(), mvelContextEntry.workingMemory, mvelContextEntry.leftTuple);
    }

    public boolean isAllowedCachedRight(LeftTuple tuple, ContextEntry context) {
        if (isUnification) {
            DroolsQuery query = ( DroolsQuery ) tuple.get( 0 ).getObject();
            Variable v = query.getVariables()[ ((UnificationContextEntry)context).getReader().getIndex() ];

            if (v != null) {
                return true;
            }
            context = ((UnificationContextEntry)context).getContextEntry();
        }

        MvelContextEntry mvelContextEntry = (MvelContextEntry)context;
        return evaluate(mvelContextEntry.right, mvelContextEntry.workingMemory, tuple);
    }

    protected boolean evaluate(Object object, InternalWorkingMemory workingMemory, LeftTuple leftTuple) {
        if (!jitted) {
            if (conditionEvaluator == null) {
                createMvelConditionEvaluator(workingMemory);
                if (TEST_JITTING && !isDynamic) { // Only for test purposes
                    boolean mvelValue = forceJitEvaluator(object, workingMemory, leftTuple);
                }
            }

            if (!isDynamic && invocationCounter.getAndIncrement() == JIT_THRESOLD) {
                jitEvaluator(object, workingMemory, leftTuple);
            }
        }
        return conditionEvaluator.evaluate(object, workingMemory, leftTuple);
    }

    protected void createMvelConditionEvaluator(InternalWorkingMemory workingMemory) {
        if (compilationUnit != null) {
            MVELDialectRuntimeData data = getMVELDialectRuntimeData(workingMemory);
            ExecutableStatement statement = (ExecutableStatement)compilationUnit.getCompiledExpression(data);
            ParserConfiguration configuration = statement instanceof CompiledExpression ?
                    ((CompiledExpression)statement).getParserConfiguration() :
                    data.getParserConfiguration();
            conditionEvaluator = new MvelConditionEvaluator(compilationUnit, configuration, statement, declarations);
        } else {
            conditionEvaluator = new MvelConditionEvaluator(getParserConfiguration(workingMemory), expression, declarations);
        }
    }

    protected boolean forceJitEvaluator(Object object, InternalWorkingMemory workingMemory, LeftTuple leftTuple) {
        boolean mvelValue;
        try {
            mvelValue = conditionEvaluator.evaluate(object, workingMemory, leftTuple);
        } catch (ClassCastException cce) {
            mvelValue = false;
        }
        jitEvaluator(object, workingMemory, leftTuple);
        return mvelValue;
    }

    protected void jitEvaluator(Object object, InternalWorkingMemory workingMemory, LeftTuple leftTuple) {
        jitted = true;
        if (TEST_JITTING) {
            executeJitting(object, workingMemory, leftTuple);
        } else {
            ExecutorHolder.executor.execute(new ConditionJitter(this, object, workingMemory, leftTuple));
        }
    }

    private static class ConditionJitter implements Runnable {
        private MvelConstraint mvelConstraint;
        private Object object;
        private InternalWorkingMemory workingMemory;
        private LeftTuple leftTuple;

        private ConditionJitter(MvelConstraint mvelConstraint, Object object, InternalWorkingMemory workingMemory, LeftTuple leftTuple) {
            this.mvelConstraint = mvelConstraint;
            this.object = object;
            this.workingMemory = workingMemory;
            this.leftTuple = leftTuple;
        }

        public void run() {
            mvelConstraint.executeJitting(object, workingMemory, leftTuple);
            mvelConstraint = null;
            object = null;
            workingMemory = null;
            leftTuple = null;
        }
    }

    private static class ExecutorHolder {
        private static final Executor executor = ExecutorProviderFactory.getExecutorProvider().getExecutor();
    }

    private void executeJitting(Object object, InternalWorkingMemory workingMemory, LeftTuple leftTuple) {
        InternalRuleBase ruleBase = ((InternalRuleBase) workingMemory.getRuleBase());
        if ( MemoryUtil.permGenStats.isUsageThresholdExceeded(ruleBase.getConfiguration().getPermGenThreshold()) ) {
            return;
        }

        CompositeClassLoader classLoader = ruleBase.getRootClassLoader();
        if (analyzedCondition == null) {
            analyzedCondition = ((MvelConditionEvaluator) conditionEvaluator).getAnalyzedCondition(object, workingMemory, leftTuple);
        }
        try {
            conditionEvaluator = ASMConditionEvaluatorJitter.jitEvaluator(expression, analyzedCondition, declarations, classLoader, leftTuple);
        } catch (Throwable t) {
            throw new RuntimeException("Exception jitting: " + expression, t);
        }
    }

    public ContextEntry createContextEntry() {
        if (declarations.length == 0) return null;
        ContextEntry contextEntry = new MvelContextEntry(declarations);
        if (isUnification) {
            contextEntry = new UnificationContextEntry(contextEntry, declarations[0]);
        }
        return contextEntry;
    }

    public FieldIndex getFieldIndex() {
        // declaration's offset can be modified by the reteoo's PatternBuilder so modify the indexingDeclaration accordingly
        indexingDeclaration.getPattern().setOffset(declarations[0].getPattern().getOffset());
        return new FieldIndex(extractor, indexingDeclaration, INDEX_EVALUATOR);
    }

    public InternalReadAccessor getFieldExtractor() {
        return extractor;
    }

    public Declaration[] getRequiredDeclarations() {
        return declarations;
    }

    public Declaration getIndexingDeclaration() {
        return indexingDeclaration;
    }

    public void replaceDeclaration(Declaration oldDecl, Declaration newDecl) {
        for (int i = 0; i < declarations.length; i++) {
            if (declarations[i].equals(oldDecl)) {
                if (compilationUnit != null) {
                    compilationUnit.replaceDeclaration(declarations[i], newDecl);
                }
                declarations[i] = newDecl;
                break;
            }
        }

        if (indexingDeclaration != null && indexingDeclaration.equals(oldDecl)) {
            indexingDeclaration = newDecl;
        }
    }

    // Slot specific

    public long getListenedPropertyMask(List<String> settableProperties) {
        if (conditionEvaluator == null) {
            return calculateMaskFromExpression(settableProperties);
        }
        if (analyzedCondition == null) {
            analyzedCondition = ((MvelConditionEvaluator) conditionEvaluator).getAnalyzedCondition();
        }
        return calculateMask(analyzedCondition, settableProperties);
    }

    private long calculateMaskFromExpression(List<String> settableProperties) {
        long mask = 0;
        String[] simpleExpressions = expression.split("\\Q&&\\E|\\Q||\\E");

        for (String simpleExpression : simpleExpressions) {
            String propertyName = getPropertyNameFromSimpleExpression(simpleExpression);
            if (propertyName.length() == 0) {
                continue;
            }
            if (propertyName.equals("this")) {
                return Long.MAX_VALUE;
            }
            int pos = settableProperties.indexOf(propertyName);
            if (pos < 0 && Character.isUpperCase(propertyName.charAt(0))) {
                propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                pos = settableProperties.indexOf(propertyName);
            }
            if (pos >= 0) { // Ignore not settable properties
                mask = BitMaskUtil.set(mask, pos);
            }
        }

        return mask;
    }

    private String getPropertyNameFromSimpleExpression(String simpleExpression) {
        StringBuilder propertyNameBuilder = new StringBuilder();
        int cursor = extractFirstIdentifier(simpleExpression, propertyNameBuilder, 0);

        String propertyName = propertyNameBuilder.toString();
        if (propertyName.equals("this")) {
            cursor = skipBlanks(simpleExpression, cursor);
            if (simpleExpression.charAt(cursor) != '.') {
                return "this";
            }
            propertyNameBuilder = new StringBuilder();
            extractFirstIdentifier(simpleExpression, propertyNameBuilder, cursor);
            propertyName = propertyNameBuilder.toString();
        }

        if (propertyName.startsWith("is") || propertyName.startsWith("get")) {
            int exprPos = simpleExpression.indexOf(propertyName);
            int propNameEnd = exprPos + propertyName.length();
            if (simpleExpression.length() > propNameEnd + 2 && simpleExpression.charAt(propNameEnd) == '(') {
                propertyName = getter2property(propertyName);
            }
        }

        return propertyName;
    }

    private long calculateMask(Condition condition, List<String> settableProperties) {
        if (condition instanceof SingleCondition) {
            return calculateMask((SingleCondition) condition, settableProperties);
        }
        long mask = 0L;
        for (Condition c : ((CombinedCondition)condition).getConditions()) {
            mask |= calculateMask(c, settableProperties);
        }
        return mask;
    }

    private long calculateMask(SingleCondition condition, List<String> settableProperties) {
        String propertyName = getFirstInvokedPropertyName(condition.getLeft());
        if (propertyName == null) {
            return Long.MAX_VALUE;
        }

        int pos = settableProperties.indexOf(propertyName);
        if (pos < 0) {
            throw new RuntimeException("Unknown property: " + propertyName);
        }
        return 1L << pos;
    }

    private String getFirstInvokedPropertyName(Expression expression) {
        if (!(expression instanceof EvaluatedExpression)) {
            return null;
        }
        List<Invocation> invocations = ((EvaluatedExpression)expression).invocations;
        Invocation invocation = invocations.get(0);

        if (invocation instanceof MethodInvocation) {
            Method method = ((MethodInvocation)invocation).getMethod();
            if (method == null && invocations.size() > 1) {
                invocation = invocations.get(1);
                if (invocation instanceof MethodInvocation) {
                    method = ((MethodInvocation)invocation).getMethod();
                } else if (invocation instanceof FieldAccessInvocation) {
                    return ((FieldAccessInvocation)invocation).getField().getName();
                }
            }
            return getter2property(method.getName());
        }

        if (invocation instanceof FieldAccessInvocation) {
            return ((FieldAccessInvocation)invocation).getField().getName();
        }

        return null;
    }

    // Externalizable

    public void writeExternal(ObjectOutput out) throws IOException {
        super.writeExternal(out);
        out.writeObject(packageName);
        out.writeObject(expression);
        out.writeObject(declarations);
        out.writeObject(indexingDeclaration);
        out.writeObject(extractor);
        out.writeObject(constraintType);
        out.writeBoolean(isUnification);
        out.writeBoolean(isDynamic);
        out.writeObject(fieldValue);
        out.writeObject(compilationUnit);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        super.readExternal(in);
        packageName = (String)in.readObject();
        expression = (String)in.readObject();
        declarations = (Declaration[]) in.readObject();
        indexingDeclaration = (Declaration) in.readObject();
        extractor = (InternalReadAccessor) in.readObject();
        constraintType = (IndexUtil.ConstraintType) in.readObject();
        isUnification = in.readBoolean();
        isDynamic = in.readBoolean();
        fieldValue = (FieldValue) in.readObject();
        compilationUnit = (MVELCompilationUnit) in.readObject();
    }

    public boolean isTemporal() {
        return false;
    }

    public MvelConstraint clone() {
        Declaration[] clonedDeclarations = new Declaration[declarations.length];
        System.arraycopy(declarations, 0, clonedDeclarations, 0, declarations.length);

        MvelConstraint clone = new MvelConstraint();
        clone.setType(getType());
        clone.packageName = packageName;
        clone.expression = expression;
        clone.constraintType = constraintType;
        clone.declarations = clonedDeclarations;
        clone.indexingDeclaration = indexingDeclaration;
        clone.extractor = extractor;
        clone.isUnification = isUnification;
        clone.isDynamic = isDynamic;
        clone.conditionEvaluator = conditionEvaluator;
        clone.compilationUnit = compilationUnit != null ? compilationUnit.clone() : null;
        return clone;
    }

    public int hashCode() {
        if (isAlphaHashable()) {
            return 29 * getLeftForEqualExpression().hashCode() + 31 * fieldValue.hashCode();
        }
        return expression.hashCode();
    }

    private String getLeftForEqualExpression() {
        return expression.substring(0, expression.indexOf("==")).trim();
    }

    private boolean isAlphaHashable() {
        return fieldValue != null && constraintType == IndexUtil.ConstraintType.EQUAL && getType() == ConstraintType.ALPHA;
    }

    public boolean equals(final Object object) {
        if ( this == object ) {
            return true;
        }
        if ( object == null || object.getClass() != MvelConstraint.class ) {
            return false;
        }
        MvelConstraint other = (MvelConstraint) object;
        if (isAlphaHashable()) {
            if ( !other.isAlphaHashable() ||
                    !getLeftForEqualExpression().equals(other.getLeftForEqualExpression()) ||
                    !fieldValue.equals(other.fieldValue) ) {
                return false;
            }
        } else {
            if (!expression.equals(other.expression)) {
                return false;
            }
        }
        if (declarations.length != other.declarations.length) {
            return false;
        }
        for (int i = 0; i < declarations.length; i++) {
            if ( !declarations[i].getExtractor().equals( other.declarations[i].getExtractor() ) ) {
                return false;
            }
        }
        return true;
    }
   
    @Override
    public String toString() {
        return expression;
    }

    protected ParserConfiguration getParserConfiguration(InternalWorkingMemory workingMemory) {
        return getMVELDialectRuntimeData(workingMemory).getParserConfiguration();
    }

    protected MVELDialectRuntimeData getMVELDialectRuntimeData(InternalWorkingMemory workingMemory) {
        return ((MVELDialectRuntimeData)workingMemory.getRuleBase().getPackage(packageName).getDialectRuntimeRegistry().getDialectData( "mvel" ));
    }

    // MvelArrayContextEntry

    public static class MvelContextEntry implements ContextEntry {

        protected ContextEntry next;
        protected LeftTuple leftTuple;
        protected Object right;
        protected Declaration[] declarations;

        protected transient InternalWorkingMemory workingMemory;

        public MvelContextEntry() { }

        public MvelContextEntry(Declaration[] declarations) {
            this.declarations = declarations;
        }

        public ContextEntry getNext() {
            return this.next;
        }

        public void setNext(final ContextEntry entry) {
            this.next = entry;
        }

        public void updateFromTuple(InternalWorkingMemory workingMemory, LeftTuple leftTuple) {
            this.leftTuple = leftTuple;
            this.workingMemory = workingMemory;
        }

        public void updateFromFactHandle(InternalWorkingMemory workingMemory, InternalFactHandle handle) {
            this.workingMemory = workingMemory;
            right = handle.getObject();
        }

        public void resetTuple() {
            leftTuple = null;
        }

        public void resetFactHandle() {
            workingMemory = null;
            right = null;
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(leftTuple);
            out.writeObject(right);
            out.writeObject(declarations);
            out.writeObject(next);
        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            leftTuple = (LeftTuple)in.readObject();
            right = in.readObject();
            declarations = (Declaration[])in.readObject();
            next = (ContextEntry)in.readObject();
        }

        public LeftTuple getLeftTuple() {
            return leftTuple;
        }

        public Object getRight() {
            return right;
        }

        public Declaration[] getDeclarations() {
            return declarations;
        }

        public InternalWorkingMemory getWorkingMemory() {
            return workingMemory;
        }
    }

    public static class UnificationContextEntry implements ContextEntry {
        private ContextEntry contextEntry;
        private Declaration declaration;
        private Variable variable;
        private ArrayElementReader reader;

        public UnificationContextEntry() { }

        public UnificationContextEntry(ContextEntry contextEntry,
                                       Declaration declaration) {
            this.contextEntry = contextEntry;
            this.declaration = declaration;
            reader = ( ArrayElementReader ) this.declaration.getExtractor();
        }

        public ContextEntry getContextEntry() {
            return this.contextEntry;
        }

        public ArrayElementReader getReader() {
            return reader;
        }

        public ContextEntry getNext() {
            return this.contextEntry.getNext();
        }

        public void resetFactHandle() {
            this.contextEntry.resetFactHandle();
        }

        public void resetTuple() {
            this.contextEntry.resetTuple();
            this.variable = null;
        }

        public void setNext(ContextEntry entry) {
            this.contextEntry.setNext( entry );
        }

        public void updateFromFactHandle(InternalWorkingMemory workingMemory,
                                         InternalFactHandle handle) {
            this.contextEntry.updateFromFactHandle( workingMemory, handle );
        }

        public void updateFromTuple(InternalWorkingMemory workingMemory,
                                    LeftTuple tuple) {
            DroolsQuery query = ( DroolsQuery ) tuple.get( 0 ).getObject();
            Variable v = query.getVariables()[ this.reader.getIndex() ];
            if ( v == null ) {
                // if there is no Variable, handle it as a normal constraint
                this.variable = null;
                this.contextEntry.updateFromTuple( workingMemory, tuple );
            } else {
                this.variable = v;
            }
        }

        public void readExternal(ObjectInput in) throws IOException,
                                                ClassNotFoundException {
            this.contextEntry = (ContextEntry) in.readObject();
            this.declaration = ( Declaration ) in.readObject();
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject( this.contextEntry );
            out.writeObject( this.declaration );
        }

        public Variable getVariable() {
            return this.variable;
        }

    }

    public static final IndexEvaluator INDEX_EVALUATOR = new PlainIndexEvaluator();
    public static class PlainIndexEvaluator implements IndexEvaluator {
        public boolean evaluate(InternalWorkingMemory workingMemory,
                                final InternalReadAccessor extractor1,
                                final Object object1,
                                final InternalReadAccessor extractor2, final Object object2) {
            final Object value1 = extractor1.getValue( workingMemory, object1 );
            final Object value2 = extractor2.getValue( workingMemory, object2 );
            if (value1 == null) {
                return value2 == null;
            }
            if (value1 instanceof String) {
                return value1.equals(value2.toString());
            }
            return value1.equals( value2 );
        }
    }
}
TOP

Related Classes of org.drools.rule.constraint.MvelConstraint$PlainIndexEvaluator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.