/** * Copyright 2015 Santhosh Kumar Tekuri * * The JLibs authors license this file to you 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 jlibs.xml.sax.dog.expr.nodset; import jlibs.core.lang.ImpossibleException; import jlibs.core.lang.NotImplementedException; import jlibs.core.util.LongTreeMap; import jlibs.xml.sax.dog.DataType; import jlibs.xml.sax.dog.Scope; import jlibs.xml.sax.dog.expr.Evaluation; import jlibs.xml.sax.dog.expr.Expression; import jlibs.xml.sax.dog.expr.Literal; import jlibs.xml.sax.dog.path.LocationPath; import jlibs.xml.sax.dog.sniff.Event; import java.util.ArrayList; import java.util.List; /** * TODO: simplify has to be implemented * * @author Santhosh Kumar T */ public class PathExpression extends Expression{ public final LocationPath union; public final Expression contexts[]; public final Expression relativeExpression; public final boolean forEach; public PathExpression(LocationPath union, Expression relativeExpression, boolean forEach){ super(Scope.DOCUMENT, relativeExpression.resultType); assert relativeExpression.scope()!=Scope.DOCUMENT; this.union = union; contexts = new Expression[union.contexts.size()]; for(int i=0; i<contexts.length; i++) contexts[i] = union.contexts.get(i).typeCast(DataType.NODESET); this.relativeExpression = relativeExpression; if(relativeExpression instanceof LocationExpression) ((LocationExpression)relativeExpression).rawResult = true; else ((Literal)relativeExpression).rawResultRequired(); this.forEach = forEach; if(union.hitExpression!=null) union.hitExpression.pathExpression = this; } @Override public Object getResult(){ return null; } @Override public Object getResult(Event event){ return new PathEvaluation(this, event); } @Override public String toString(){ StringBuilder buff = new StringBuilder(); for(Expression context: contexts){ if(buff.length()>0) buff.append(", "); buff.append(context); } if(union.predicateSet.getPredicate()!=null){ buff.insert(0, '('); buff.append(')'); buff.append('['); buff.append(union.predicateSet.getPredicate()); buff.append(']'); } return String.format("path-expression(context(%s), %s, %s)", buff, relativeExpression, forEach); } public static class HitExpression extends Expression{ public PathExpression pathExpression; public HitExpression(){ super(Scope.LOCAL, DataType.BOOLEAN); } @Override public Object getResult(){ throw new ImpossibleException(); } @Override public Object getResult(Event event){ PathEvaluation pathEvaluation = (PathEvaluation)event.result(pathExpression); return pathEvaluation.evaluations.get(event.order()); } } } final class PathEvaluation extends Evaluation<PathExpression> implements NodeSetListener, NodeSetListener.Support{ private Event event; private PositionTracker positionTracker; public PathEvaluation(PathExpression expression, Event event){ super(expression, event.order()); this.event = event; contextsPending = expression.contexts.length; if(expression.union.predicateSet.hasPosition) positionTracker = new PositionTracker(expression.union.predicateSet.headPositionalPredicate); } @Override public void start(){ for(Expression context: expression.contexts){ Object result = event.evaluate(context); if(result==null){ Object eval = event.result(context); if(eval instanceof LocationEvaluation) ((LocationEvaluation)eval).nodeSetListener = this; else ((PathEvaluation)eval).nodeSetListener = this; }else throw new NotImplementedException(); } } protected LongTreeMap<EvaluationInfo> evaluations = new LongTreeMap<EvaluationInfo>(); @Override public void mayHit(){ long order = event.order(); EvaluationInfo evalInfo = evaluations.get(order); if(evalInfo==null){ evaluations.put(order, evalInfo=new EvaluationInfo(event, expression.union.hitExpression, order, nodeSetListener)); if(positionTracker!=null){ event.positionTrackerStack.addFirst(positionTracker); positionTracker.addEvaluation(event); } Expression predicate = expression.union.predicateSet.getPredicate(); Object predicateResult = predicate==null ? Boolean.TRUE : event.evaluate(predicate); if(predicateResult==Boolean.TRUE){ Object r = event.evaluate(expression.relativeExpression); if(r==null){ event.evaluation.addListener(this); event.evaluation.start(); evalInfo.eval = event.evaluation; pendingCount++; if(nodeSetListener!=null){ if(event.evaluation instanceof LocationEvaluation) ((LocationEvaluation)event.evaluation).nodeSetListener = evalInfo; else ((PathEvaluation)event.evaluation).nodeSetListener = evalInfo; } }else{ if(nodeSetListener!=null) nodeSetListener.mayHit(); evalInfo.setResult(r); } }else if(predicateResult==null){ Evaluation predicateEvaluation = event.evaluation; Object resultItem = expression.relativeExpression.getResult(event); if(nodeSetListener!=null && !(nodeSetListener instanceof Event)){ // nodeSetListener will be event if xmlBuilder is set if(resultItem instanceof LocationEvaluation) ((LocationEvaluation)resultItem).nodeSetListener = evalInfo; else ((PathEvaluation)resultItem).nodeSetListener = evalInfo; } Evaluation childEval = new PredicateEvaluation(expression.relativeExpression, event.order(), resultItem, event, predicate, predicateEvaluation); childEval.addListener(this); childEval.start(); evalInfo.eval = childEval; pendingCount++; }else throw new ImpossibleException(); } evalInfo.hitCount++; if(evalInfo.hitCount==1 && positionTracker!=null){ positionTracker.startEvaluation(); event.positionTrackerStack.pollFirst(); } } @Override public void discard(long order){ LongTreeMap.Entry<EvaluationInfo> entry = evaluations.getEntry(order); if(entry!=null){ if(entry.value.discard()==0){ evaluations.deleteEntry(entry); if(entry.value.eval!=null){ pendingCount--; entry.value.eval.removeListener(this); } } } } private int contextsPending; @Override public void finished(){ contextsPending--; if(contextsPending==0){ if(expression.union.hitExpression!=null){ for(EvaluationInfo evalInfo: new ArrayList<EvaluationInfo>(evaluations.values())) evalInfo.doFinish(); if(positionTracker!=null) positionTracker.expired(); } } tryToFinish(); } private int pendingCount; private int pendingCount(){ int count = 0; for(LongTreeMap.Entry<EvaluationInfo> entry = evaluations.firstEntry(); entry!=null; entry=entry.next()){ if(entry.value.eval!=null) count++; } return count; } private Object finalResult; private void tryToFinish(){ if(finalResult==null){ if(contextsPending>0) return; assert pendingCount==pendingCount(); if(pendingCount==0){ finalResult = computeResult(); if(nodeSetListener!=null) nodeSetListener.finished(); fireFinished(); } } } @SuppressWarnings({"unchecked"}) public Object computeResult(){ if(expression.forEach){ List<Object> result = new ArrayList<Object>(evaluations.size()); for(LongTreeMap.Entry entry = evaluations.firstEntry(); entry!=null; entry=entry.next()) result.add(computeResultItem(((EvaluationInfo)entry.value).result)); return result; }else{ LongTreeMap result = new LongTreeMap(); for(LongTreeMap.Entry<EvaluationInfo> entry = evaluations.firstEntry(); entry!=null; entry=entry.next()) result.putAll(entry.value.result); return computeResultItem(result); } } @SuppressWarnings({"unchecked", "UnnecessaryBoxing"}) private Object computeResultItem(LongTreeMap result){ switch(expression.resultType){ case NODESET: case STRINGS: case NUMBERS: return new ArrayList(result.values()); case NUMBER: if(expression.relativeExpression instanceof Count) return new Double(result.size()); else{ double d = 0; for(LongTreeMap.Entry entry=result.firstEntry(); entry!=null; entry=entry.next()) d += (Double)entry.value; return d; } case BOOLEAN: return !result.isEmpty(); default: if(result.isEmpty()) return expression.resultType.defaultValue; else return result.firstEntry().value; } } @Override public Object getResult(){ return finalResult; } @Override @SuppressWarnings({"unchecked"}) public void finished(Evaluation evaluation){ LongTreeMap.Entry<EvaluationInfo> entry = evaluations.getEntry(evaluation.order); assert entry.value.eval==evaluation; if(evaluation instanceof PredicateEvaluation){ PredicateEvaluation predicateEvaluation = (PredicateEvaluation)evaluation; if(predicateEvaluation.result!=null){ if(predicateEvaluation.result instanceof Evaluation){ entry.value.eval = (Evaluation)predicateEvaluation.result; entry.value.eval.addListener(this); return; }else{ entry.value.setResult(predicateEvaluation.result); entry.value.eval = null; pendingCount--; } }else{ entry.value.doDiscards(); evaluations.deleteEntry(entry); if(entry.value.eval!=null) pendingCount--; } }else{ entry.value.setResult(evaluation.getResult()); entry.value.eval = null; pendingCount--; } tryToFinish(); } private NodeSetListener nodeSetListener; @Override public void setNodeSetListener(NodeSetListener nodeSetListener){ this.nodeSetListener = nodeSetListener; } } final class EvaluationInfo extends Evaluation<PathExpression.HitExpression> implements NodeSetListener{ Event event; Evaluation eval; LongTreeMap result; EvaluationInfo(Event event, PathExpression.HitExpression expression, long order, NodeSetListener nodeSetListener){ super(expression, order); this.event = event; this.nodeSetListener = nodeSetListener; if(nodeSetListener!=null) mayHits = new ArrayList<Long>(); } @SuppressWarnings({"unchecked"}) public void setResult(Object result){ if(result instanceof LongTreeMap) this.result = (LongTreeMap)result; else{ this.result = new LongTreeMap(); this.result.put(order, result); } } public int hitCount; private Boolean hit; public int discard(){ if(--hitCount==0){ hit = Boolean.FALSE; doDiscards(); if(listener!=null) fireFinished(); } return hitCount; } public void doFinish(){ hit = Boolean.TRUE; if(listener!=null) fireFinished(); } @Override public void start(){} @Override public Object getResult(){ return hit; } @Override public void finished(Evaluation evaluation){} /*-------------------------------------------------[ NodeSetListener ]---------------------------------------------------*/ public NodeSetListener nodeSetListener; private List<Long> mayHits; @Override public void mayHit(){ mayHits.add(event.order()); nodeSetListener.mayHit(); } public void doDiscards(){ if(nodeSetListener!=null){ for(long order: mayHits) nodeSetListener.discard(order); } } @Override public void discard(long order){ mayHits.remove(order); nodeSetListener.discard(order); } @Override public void finished(){} }