/* * 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<XpathChunk> chunks; private Declaration declaration; private Declaration xpathStartDeclaration; public XpathConstraint() { this(new LinkedList<XpathChunk>()); } private XpathConstraint(LinkedList<XpathChunk> 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<XpathChunk>) 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<XpathChunk> 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>(), object); } private List<Object> evaluateObject(InternalWorkingMemory workingMemory, Tuple leftTuple, XpathChunk chunk, List<Object> 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<Constraint> 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<Constraint>) 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<Constraint>(); } 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<AlphaNodeFieldConstraint> getAlphaConstraints() { return getConstraintsByType(ConstraintType.ALPHA); } public List<BetaNodeFieldConstraint> getBetaConstraints() { return getConstraintsByType(ConstraintType.BETA); } public List<XpathConstraint> getXpathConstraints() { return getConstraintsByType(ConstraintType.XPATH); } private <T> List<T> getConstraintsByType(ConstraintType constraintType) { if (constraints == null) { return Collections.emptyList(); } List<T> typedConstraints = new ArrayList<T>(); 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; } } }