All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.drools.core.rule.constraint.XpathConstraint Maven / Gradle / Ivy

There is a newer version: 9.44.0.Final
Show newest version
/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      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 org.drools.core.rule.constraint;

import org.drools.core.base.ClassObjectType;
import org.drools.core.base.DroolsQuery;
import org.drools.core.common.InternalFactHandle;
import org.drools.core.common.InternalWorkingMemory;
import org.drools.core.phreak.ReactiveObject;
import org.drools.core.rule.ContextEntry;
import org.drools.core.rule.Declaration;
import org.drools.core.rule.From;
import org.drools.core.rule.MutableTypeConstraint;
import org.drools.core.spi.AcceptsClassObjectType;
import org.drools.core.spi.AlphaNodeFieldConstraint;
import org.drools.core.spi.BetaNodeFieldConstraint;
import org.drools.core.spi.Constraint;
import org.drools.core.spi.DataProvider;
import org.drools.core.spi.InternalReadAccessor;
import org.drools.core.spi.PatternExtractor;
import org.drools.core.spi.PropagationContext;
import org.drools.core.spi.Tuple;
import org.drools.core.util.ClassUtils;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static org.drools.core.util.ClassUtils.areNullSafeEquals;
import static org.drools.core.util.ClassUtils.convertFromPrimitiveType;

public class XpathConstraint extends MutableTypeConstraint {

    private LinkedList chunks;

    private Declaration declaration;
    private Declaration xpathStartDeclaration;

    public XpathConstraint() {
        this(new LinkedList());
    }

    private XpathConstraint(LinkedList chunks) {
        this.chunks = chunks;
    }

    public XpathChunk addChunck(Class clazz, String field, int index, boolean iterate, boolean lazy) {
        XpathChunk chunk = XpathChunk.get(clazz, field, index, iterate, lazy);
        if (chunk != null) {
            chunks.add(chunk);
        }
        return chunk;
    }

    @Override
    public ConstraintType getType() {
        return ConstraintType.XPATH;
    }

    @Override
    public Declaration[] getRequiredDeclarations() {
        // TODO ?
        return new Declaration[0];
    }

    @Override
    public void replaceDeclaration(Declaration oldDecl, Declaration newDecl) {
        throw new UnsupportedOperationException();
    }

    @Override
    public XpathConstraint clone() {
        XpathConstraint clone = new XpathConstraint(this.chunks);
        if (declaration != null) {
            clone.setDeclaration( declaration.clone() );
        }
        if (xpathStartDeclaration != null) {
            clone.setXpathStartDeclaration( xpathStartDeclaration.clone() );
        }
        return clone;
    }

    @Override
    public boolean isTemporal() {
        // TODO ?
        return false;
    }

    @Override
    public boolean isAllowedCachedLeft(ContextEntry context, InternalFactHandle handle) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isAllowedCachedRight(Tuple tuple, ContextEntry context) {
        throw new UnsupportedOperationException();
    }

    @Override
    public ContextEntry createContextEntry() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean isAllowed(InternalFactHandle handle, InternalWorkingMemory workingMemory) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        chunks =  (LinkedList) in.readObject();
        declaration = (Declaration) in.readObject();
        xpathStartDeclaration = (Declaration) in.readObject();
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(chunks);
        out.writeObject(declaration);
        out.writeObject(xpathStartDeclaration);
    }

    public LinkedList getChunks() {
        return chunks;
    }

    public Class getResultClass() {
        return chunks.isEmpty() ? Object.class : chunks.getLast().getReturnedClass();
    }

    public Declaration getDeclaration() {
        return declaration;
    }

    public void setDeclaration(Declaration declaration) {
        declaration.setReadAccessor( getReadAccessor() );
        this.declaration = declaration;
    }

    public InternalReadAccessor getReadAccessor() {
        return new PatternExtractor( new ClassObjectType( getResultClass() ) );
    }

    public Declaration getXpathStartDeclaration() {
        return xpathStartDeclaration;
    }

    public void setXpathStartDeclaration( Declaration xpathStartDeclaration ) {
        this.xpathStartDeclaration = xpathStartDeclaration;
        chunks.get(0).declaration = xpathStartDeclaration;
    }

    private interface XpathEvaluator {
        Iterable evaluate(InternalWorkingMemory workingMemory, Tuple leftTuple, Object object);
    }

    @Override
    public String toString() {
        return chunks.toString();
    }

    private static class SingleChunkXpathEvaluator implements XpathEvaluator {
        private final XpathChunk chunk;

        private SingleChunkXpathEvaluator(XpathChunk chunk) {
            this.chunk = chunk;
        }

        public Iterable evaluate(InternalWorkingMemory workingMemory, Tuple leftTuple, Object object) {
            return evaluateObject(workingMemory, leftTuple, chunk, new ArrayList(), object);
        }

        private List evaluateObject(InternalWorkingMemory workingMemory, Tuple leftTuple, XpathChunk chunk, List list, Object object) {
            Object result = chunk.evaluate(object);
            if (!chunk.lazy && result instanceof ReactiveObject) {
                ((ReactiveObject) result).addLeftTuple(leftTuple);
            }
            if (chunk.iterate && result instanceof Iterable) {
                for (Object value : (Iterable) result) {
                    if (!chunk.lazy && value instanceof ReactiveObject) {
                        ((ReactiveObject) value).addLeftTuple(leftTuple);
                    }
                    if (value != null) {
                        list.add(value);
                    }
                }
            } else if (result != null) {
                list.add(result);
            }
            return list;
        }

        @Override
        public String toString() {
            return chunk.toString();
        }

        @Override
        public boolean equals( Object obj ) {
            if (this == obj) {
                return true;
            }
            if (obj == null || !(obj instanceof SingleChunkXpathEvaluator)) {
                return false;
            }

            SingleChunkXpathEvaluator other = (SingleChunkXpathEvaluator) obj;

            return chunk.equals( other.chunk );
        }

        @Override
        public int hashCode() {
            return chunk.hashCode();
        }
    }

    public static class XpathChunk implements AcceptsClassObjectType, Externalizable {

        private String field;
        private int index;
        private boolean iterate;
        private boolean lazy;
        private boolean array;

        private List constraints;
        private Declaration declaration;
        private ClassObjectType classObjectType;
        private ClassObjectType returnedType;
        private Method accessor;
        
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(field);
            out.writeInt(index);
            out.writeBoolean(iterate);
            out.writeBoolean(lazy);
            out.writeBoolean(array);
            
            out.writeObject(constraints);
            out.writeObject(declaration);
            out.writeObject(classObjectType);
            out.writeObject(returnedType);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            field = (String) in.readObject();
            index = in.readInt();
            iterate = in.readBoolean();
            lazy = in.readBoolean();
            array = in.readBoolean();
            
            constraints = (List) in.readObject();
            declaration = (Declaration) in.readObject();
            classObjectType = (ClassObjectType) in.readObject();
            returnedType = (ClassObjectType) in.readObject();
        }
        
        /**
         * NOT INTENDED FOR ACTUAL USE, only for Java Serialization mechanism purpose only.
         */
        public XpathChunk() {
            // for serialization only purposes.
        }

        private XpathChunk(String field, int index, boolean iterate, boolean lazy, boolean array) {
            this.field = field;
            this.index = index;
            this.iterate = iterate;
            this.lazy = lazy;
            this.array = array;
        }

        private static XpathChunk get(Class clazz, String field, int index, boolean iterate, boolean lazy) {
            Method accessor = ClassUtils.getAccessor( clazz, field );
            if (accessor == null) {
                return null;
            }
            return new XpathChunk(accessor.getName(), index, iterate, lazy, iterate && accessor.getReturnType().isArray());
        }

        private Method getAccessor() {
            if (accessor == null) {
                try {
                    accessor = classObjectType.getClassType().getMethod( field );
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException( e );
                }
            }
            return accessor;
        }

        public void addConstraint(Constraint constraint) {
            if (constraints == null) {
                constraints = new ArrayList();
            }
            setConstraintType((MutableTypeConstraint)constraint);
            constraints.add(constraint);
        }

        private void setConstraintType(final MutableTypeConstraint constraint) {
            final Declaration[] declarations = constraint.getRequiredDeclarations();

            boolean isAlphaConstraint = true;
            for ( int i = 0; isAlphaConstraint && i < declarations.length; i++ ) {
                if ( !declarations[i].isGlobal() ) {
                    isAlphaConstraint = false;
                }
            }

            ConstraintType type = isAlphaConstraint ? ConstraintType.ALPHA : ConstraintType.BETA;
            constraint.setType(type);
        }

        public Object evaluate(Object obj) {
            try {
                Object result = getAccessor().invoke( obj );
                if (array) {
                    result = Arrays.asList( (Object[]) result );
                }
                if (index >= 0) {
                    result = ((List)result).subList( index, index+1 );
                }
                return result;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public Class getReturnedClass() {
            if (returnedType != null) {
                return returnedType.getClassType();
            }
            try {
                Method accessor = classObjectType.getClassType().getMethod( field );
                return convertFromPrimitiveType( iterate ? getItemClass(accessor) : accessor.getReturnType() );
            } catch (NoSuchMethodException e) {
                throw new RuntimeException( e );
            }
        }

        public void setReturnedType( ClassObjectType returnedType ) {
            this.returnedType = returnedType;
        }

        private Class getItemClass(Method accessor) {
            Class lastReturnedClass = accessor.getReturnType();
            if (Iterable.class.isAssignableFrom(lastReturnedClass)) {
                return getParametricType(accessor);
            }
            if (lastReturnedClass.isArray()) {
                return lastReturnedClass.getComponentType();
            }
            return lastReturnedClass;
        }

        private Class getParametricType(Method accessor) {
            Type returnedType = accessor.getGenericReturnType();
            if (returnedType instanceof ParameterizedType) {
                Type[] parametricType = ((ParameterizedType) returnedType).getActualTypeArguments();
                if (parametricType.length > 0) {
                    return parametricType[0] instanceof Class ?
                           (Class) parametricType[0] :
                           (Class) ( (ParameterizedType) parametricType[0] ).getRawType();
                }
            }
            return Object.class;
        }

        public From asFrom() {
            From from = new From( new XpathDataProvider(new SingleChunkXpathEvaluator(this), declaration ) );
            from.setResultClass(getReturnedClass());
            return from;
        }

        public List getAlphaConstraints() {
            return getConstraintsByType(ConstraintType.ALPHA);
        }

        public List getBetaConstraints() {
            return getConstraintsByType(ConstraintType.BETA);
        }

        public List getXpathConstraints() {
            return getConstraintsByType(ConstraintType.XPATH);
        }

        private  List getConstraintsByType(ConstraintType constraintType) {
            if (constraints == null) {
                return Collections.emptyList();
            }
            List typedConstraints = new ArrayList();
            for (Constraint constraint : constraints) {
                if (constraint.getType() == constraintType) {
                    typedConstraints.add( (T) constraint );
                }
            }
            return typedConstraints;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder( (lazy ? "?" : "") + classObjectType.getClassType().getSimpleName() );
            if (iterate) {
                sb.append( "/" );
            } else {
                sb.append( "." );
            }
            sb.append( field );
            if (index >= 0) {
                sb.append( "[" ).append( index ).append( "]" );
            }
            if (constraints != null && !constraints.isEmpty()) {
                sb.append( "{" );
                sb.append( constraints.get(0) );
                for (int i = 1; i < constraints.size(); i++) {
                    sb.append( ", " ).append( constraints.get( i ) );
                }
                sb.append( "}" );
            }
            return sb.toString();
        }

        @Override
        public void setClassObjectType( ClassObjectType classObjectType ) {
            this.classObjectType = classObjectType;
        }

        @Override
        public boolean equals( Object obj ) {
            if (this == obj) {
                return true;
            }
            if (obj == null || !(obj instanceof XpathChunk)) {
                return false;
            }

            XpathChunk other = (XpathChunk) obj;

            return field.equals( other.field ) &&
                   index == other.index &&
                   iterate == other.iterate &&
                   lazy == other.lazy &&
                   array == other.array &&
                   areNullSafeEquals(declaration, other.declaration);
        }

        @Override
        public int hashCode() {
            int hash = 23 * field.hashCode() + 29 * index;
            if (declaration != null) {
                hash += 31 * declaration.hashCode();
            }
            if (iterate) {
                hash += 37;
            }
            if (lazy) {
                hash += 41;
            }
            if (array) {
                hash += 43;
            }
            return hash;
        }
    }

    public static class XpathDataProvider implements DataProvider {

        private final XpathEvaluator xpathEvaluator;
        private final Declaration declaration;

        public XpathDataProvider( XpathEvaluator xpathEvaluator, Declaration declaration ) {
            this.xpathEvaluator = xpathEvaluator;
            this.declaration = declaration;
        }

        @Override
        public Declaration[] getRequiredDeclarations() {
            return new Declaration[0];
        }

        @Override
        public Object createContext() {
            return null;
        }

        @Override
        public Iterator getResults(Tuple leftTuple, InternalWorkingMemory wm, PropagationContext ctx, Object providerContext) {
            InternalFactHandle fh = leftTuple.getFactHandle();
            Object obj = fh.getObject();

            if (obj instanceof DroolsQuery) {
                obj = ((DroolsQuery)obj).getElements()[declaration.getPattern().getOffset()];
            }

            return xpathEvaluator.evaluate(wm, leftTuple, obj).iterator();
        }

        @Override
        public DataProvider clone() {
            return this;
        }

        @Override
        public void replaceDeclaration(Declaration declaration, Declaration resolved) {
            throw new UnsupportedOperationException();
        }

        @Override
        public String toString() {
            return xpathEvaluator.toString();
        }

        @Override
        public boolean equals( Object obj ) {
            if (this == obj) {
                return true;
            }
            if (obj == null || !(obj instanceof XpathChunk)) {
                return false;
            }

            XpathDataProvider other = (XpathDataProvider) obj;

            return xpathEvaluator.equals( other.xpathEvaluator ) &&
                   areNullSafeEquals(declaration, other.declaration);
        }

        @Override
        public int hashCode() {
            int hash = 31 * xpathEvaluator.hashCode();
            if (declaration != null) {
                hash += 37 * declaration.hashCode();
            }
            return hash;
        }

        @Override
        public boolean isReactive() {
            return true;
        }
    }
}