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

jlibs.xml.sax.dog.expr.nodset.LocationEvaluation Maven / Gradle / Ivy

/**
 * 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.util.LongTreeMap;
import jlibs.xml.sax.dog.DataType;
import jlibs.xml.sax.dog.NodeItem;
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.LinkableEvaluation;
import jlibs.xml.sax.dog.path.AxisListener;
import jlibs.xml.sax.dog.path.EventID;
import jlibs.xml.sax.dog.path.Step;
import jlibs.xml.sax.dog.sniff.Event;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Santhosh Kumar T
 */
public final class LocationEvaluation extends AxisListener implements NodeSetListener.Support{
    private final Event event;
    private final EventID eventID;
    private final int index;
    private final boolean lastStep;

    private final Step currentStep;
    private final boolean exactPosition;
    private Evaluation predicateEvaluation;
    private Boolean predicateResult = Boolean.TRUE;

    private PositionTracker positionTracker;
    private int predicateChain = -1;

    protected LocationEvaluation(LocationExpression expression, int stepIndex, Event event, EventID eventID){
        super(expression, event.order());
        this.event = event;
        this.eventID = eventID;
        this.index = stepIndex;
        lastStep = index==expression.locationPath.steps.length-1;

        if(expression instanceof Strings)
            stringEvaluations = new ArrayList();

        currentStep = expression.locationPath.steps[stepIndex];
        exactPosition = currentStep.predicateSet.getPredicate() instanceof ExactPosition;
        if(currentStep.predicateSet.hasPosition)
            positionTracker = new PositionTracker(currentStep.predicateSet.headPositionalPredicate);
    }

    private LocationEvaluation(LocationExpression expression, int stepIndex, Event event, EventID eventID, Expression predicate, Evaluation predicateEvaluation){
        this(expression, stepIndex, event, eventID);
        predicateResult = null;
        if(predicateEvaluation==null)
            this.predicateEvaluation = event.addListener(predicate, this);
        else{
            this.predicateEvaluation = predicateEvaluation;
            predicateEvaluation.addListener(this);
        }
    }

    @Override
    public void start(){
        assert predicateResult!=Boolean.FALSE;
        assert !finished;

        if(event.hasInstantListener(expression)){
            if(listener instanceof LocationEvaluation)
                predicateChain = ((LocationEvaluation)listener).predicateChain;
            else
                predicateChain = 0;
            if(predicateEvaluation!=null)
                predicateChain++;
        }

        if(predicateEvaluation!=null){
            Expression predicate = predicateEvaluation.expression;
            if(predicate.scope()!=Scope.DOCUMENT)
                predicateEvaluation.start();
        }
        eventID.addListener(event, currentStep, this);
    }

    private LinkableEvaluation pendingEvaluationHead, pendingEvaluationTail;
    private List stringEvaluations;

    @Override
    public void onHit(EventID eventID){
        assert !finished : "getting events even after finish";

        final LocationExpression expression = this.expression;
        if(!lastStep){
            if(eventID.isEmpty(expression.locationPath.steps[index+1].axis))
                return;
        }

        final Event event = this.event;

        if(positionTracker!=null){
            event.positionTrackerStack.addFirst(positionTracker);
            positionTracker.addEvaluation(event);
        }
        LinkableEvaluation childEval = null;

        Expression predicate = currentStep.predicateSet.getPredicate();
        Object predicateResult = predicate==null ? Boolean.TRUE : event.evaluate(predicate);
        if(predicateResult==Boolean.TRUE){
            if(lastStep)
                consume(event);
            else
                childEval = new LocationEvaluation(expression, index+1, event, eventID);
        }else if(predicateResult==null){
            Evaluation predicateEvaluation = event.evaluation;
            if(lastStep){
                childEval = new PredicateEvaluation(expression, event.order(), expression.getResultItem(event), event, predicate, predicateEvaluation);
                if(nodeSetListener !=null)
                    nodeSetListener.mayHit();
            }else
                childEval = new LocationEvaluation(expression, index+1, event, eventID, predicate, predicateEvaluation);
        }

        if(childEval!=null){
            if(childEval instanceof LocationEvaluation)
                ((LocationEvaluation)childEval).nodeSetListener = nodeSetListener;
            else
                ((PredicateEvaluation)childEval).nodeSetListener = nodeSetListener;
            
            childEval.addListener(this);
            if(pendingEvaluationTail!=null){
                pendingEvaluationTail.next = childEval;
                childEval.previous = pendingEvaluationTail;
                pendingEvaluationTail = childEval;
            }else
                pendingEvaluationHead = pendingEvaluationTail = childEval;
            childEval.start();
        }

        if(positionTracker!=null){
            positionTracker.startEvaluation();
            event.positionTrackerStack.pollFirst();
        }
        if(exactPosition && predicateResult==Boolean.TRUE){
            manuallyExpired = true;
            expired();
        }
    }

    /*-------------------------------------------------[ Stages ]---------------------------------------------------*/

    private boolean expired = false;

    @Override
    public void expired(){
        assert !expired;
        expired = true;

        if(positionTracker!=null)
            positionTracker.expired();
        if(pendingEvaluationHead==null)
            resultPrepared();
    }

    private boolean resultPrepared = false;
    private void resultPrepared(){
        if(!resultPrepared){
            manuallyExpired = true;
            resultPrepared = true;

            for(LinkableEvaluation pendingEval=pendingEvaluationHead; pendingEval!=null; pendingEval=pendingEval.next)
                pendingEval.removeListener(this);
            pendingEvaluationHead = pendingEvaluationTail = null;
        }
        if(predicateResult!=null && (index!=0 || (stringEvaluations==null || stringEvaluations.size()==0)))
            finished();
        else if(result.size()==0 && predicateResult==null){ // when result is empty, there is no need to wait for predicateEvaluation to finish
            Expression predicate = predicateEvaluation.expression;
            if(predicate.scope()!=Scope.DOCUMENT)
                predicateEvaluation.removeListener(this);
            else
                event.removeListener(predicate, this);
            finished();
        }
    }

    private boolean finished = false;
    private void finished(){
        if(!finished){
            finished = true;
            for(LinkableEvaluation pendingEval=pendingEvaluationHead; pendingEval!=null; pendingEval=pendingEval.next)
                pendingEval.removeListener(this);
            pendingEvaluationHead = pendingEvaluationTail = null;
            fireFinished();
        }
    }

    @Override
    protected void fireFinished(){
        if(index==0 && nodeSetListener !=null)
            nodeSetListener.finished();
        super.fireFinished();
        if(stringEvaluations!=null){
            for(Evaluation stringEval: stringEvaluations)
                stringEval.removeListener(this);
        }
    }

    @Override
    protected void dispose(){
        if(nodeSetListener !=null){
            for(LongTreeMap.Entry entry = result.firstEntry(); entry!=null ; entry = entry.next())
                nodeSetListener.discard(entry.getKey());
        }
        manuallyExpired = true;
        for(LinkableEvaluation pendingEval=pendingEvaluationHead; pendingEval!=null; pendingEval=pendingEval.next)
            pendingEval.removeListener(this);
        pendingEvaluationHead = pendingEvaluationTail = null;
        if(predicateResult==null)
            predicateEvaluation.removeListener(this);
    }

    /*-------------------------------------------------[ Result Management ]---------------------------------------------------*/

    private LongTreeMap result = new LongTreeMap();

    private void consumedResult(){
        int resultSize = result.size();
        if(resultSize>0 && !expression.many){
            if(resultSize>1)
                result.deleteEntry(result.lastEntry());
            if(expression.first){
                if(pendingEvaluationHead==null || result.firstEntry().getKey()<=pendingEvaluationHead.order)
                    resultPrepared();
                else if(!expired){
                    manuallyExpired = true;
                    expired = true;
                }
            }else
                resultPrepared();
        }else if(expired && pendingEvaluationHead==null)
            resultPrepared();
    }

    private void consume(Event event){
        assert lastStep;
        Object resultItem = expression.getResultItem(event);
        if(resultItem instanceof Evaluation){
            Evaluation eval = (Evaluation)resultItem;
            stringEvaluations.add(eval);
            eval.addListener(this);
            eval.start();
        }else if(predicateChain==0){
            event.onInstantResult(expression, (NodeItem)resultItem);
            resultItem = Event.DUMMY_VALUE;
        }
        assert resultItem!=null : "ResultItem should be non-null";
        result.put(event.order(), resultItem);
        consumedResult();
        if(nodeSetListener !=null)
            nodeSetListener.mayHit();
    }

    private void consumeChildEvaluation(long order, Object resultItem){
        boolean prepareResult = false;
        if(expression.resultType==DataType.NUMBER){
            if(resultItem instanceof Double && ((Double)resultItem).isNaN()){
                result.clear();
                prepareResult = true;
            }
        }

        if(predicateChain==0){
            event.onInstantResult(expression, (NodeItem)resultItem);
            resultItem = Event.DUMMY_VALUE;
        }
        result.put(order, resultItem);
        consumedResult();

        if(prepareResult)
            resultPrepared();
    }

    private void consumeChildEvaluation(LongTreeMap childResult){
        boolean prepareResult = false;
        int size = childResult.size();
        if(size==1 && expression.resultType==DataType.NUMBER){
            Object resultItem = childResult.firstEntry().value;
            if(resultItem instanceof Double && ((Double)resultItem).isNaN()){
                result.clear();
                prepareResult = true;
            }
        }

        if(size>0){
            if(predicateChain==0)
                fireInstantResult(childResult);
            if(result.size()>0){
                if(nodeSetListener !=null){
                    for(LongTreeMap.Entry entry = childResult.firstEntry(); entry!=null ; entry = entry.next()){
                        if(result.put(entry.getKey(), entry.value)!=null)
                            nodeSetListener.discard(entry.getKey());
                    }
                }else
                    result.putAll(childResult);
            }else
                result = childResult;
        }
        consumedResult();

        if(prepareResult)
            resultPrepared();
    }

    private void remove(LinkableEvaluation eval){
        LinkableEvaluation prev = eval.previous;
        LinkableEvaluation next = eval.next;

        if(prev!=null)
            prev.next = next;
        else
            pendingEvaluationHead = next;

        if(next!=null)
            next.previous = prev;
        else
            pendingEvaluationTail = prev;
    }

    private void fireInstantResult(LongTreeMap result){
        LongTreeMap.Entry entry = result.firstEntry();
        while(entry!=null){
            if(entry.value instanceof NodeItem){
                event.onInstantResult(expression, (NodeItem)entry.value);
                entry.value = Event.DUMMY_VALUE;
            }
            entry = entry.next();
        }
        result.clear(); // to avoid memory leak
    }

    private void decreasePredicateChain(){
        predicateChain--;
        for(LinkableEvaluation pendingEval=pendingEvaluationHead; pendingEval!=null; pendingEval=pendingEval.next){
            if(pendingEval instanceof LocationEvaluation)
                ((LocationEvaluation)pendingEval).decreasePredicateChain();
        }
        if(predicateChain==0)
            fireInstantResult(result);
    }

    @Override
    public final void finished(Evaluation evaluation){
        assert !finished : "can't consume evaluation result after finish";

        if(evaluation==predicateEvaluation){
            predicateResult = (Boolean)evaluation.getResult();
            assert predicateResult!=null : "evaluation result should be non-null";
            if(predicateResult==Boolean.FALSE){
                if(nodeSetListener !=null){
                    for(LongTreeMap.Entry entry = result.firstEntry(); entry!=null ; entry = entry.next())
                        nodeSetListener.discard(entry.getKey());
                }
                result.clear();
                if(stringEvaluations!=null){
                    for(Evaluation stringEval: stringEvaluations)
                        stringEval.removeListener(this);
                    stringEvaluations = null;
                }
                resultPrepared();
            }else{
                if(predicateChain!=-1)
                    decreasePredicateChain();
                if(resultPrepared)
                    finished();
            }
        }else if(evaluation instanceof PredicateEvaluation){
            PredicateEvaluation predicateEvaluation = (PredicateEvaluation)evaluation;
            remove(predicateEvaluation);

            if(predicateEvaluation.result!=null){
                Object resultItem = predicateEvaluation.result;
                if(resultItem instanceof Evaluation){
                    Evaluation stringEval = (Evaluation)resultItem;
                    stringEvaluations.add(stringEval);
                    stringEval.addListener(this);
                }
                consumeChildEvaluation(predicateEvaluation.order, resultItem);
            }else{
                if(nodeSetListener !=null)
                    nodeSetListener.discard(predicateEvaluation.order);
                consumedResult();
            }
        }else if(evaluation instanceof LocationEvaluation){
            LocationEvaluation locEval = (LocationEvaluation)evaluation;
            remove(locEval);

            if(locEval.stringEvaluations!=null){
                for(Evaluation stringEval: locEval.stringEvaluations)
                    stringEval.addListener(this);
                stringEvaluations.addAll(locEval.stringEvaluations);
            }
            boolean wasExpired = expired;
            consumeChildEvaluation(locEval.result);
            if(!wasExpired && expired){
                assert !finished;
                LinkableEvaluation eval = locEval.next;
                while(eval!=null){
                    eval.removeListener(this);
                    remove(eval);
                    eval = eval.next;
                }
                if(pendingEvaluationHead==null)
                    resultPrepared();
            }
        }else{
            stringEvaluations.remove(evaluation);
            consumeChildEvaluation(evaluation.order, evaluation.getResult());
        }
    }

    private Object finalResult;
    public Object getResult(){
        if(index==0 && predicateChain==0)
            return null;

        if(finalResult==null)
            finalResult = expression.getResult(result);
        return finalResult;
    }

    public NodeSetListener nodeSetListener;

    @Override
    public void setNodeSetListener(NodeSetListener nodeSetListener){
        this.nodeSetListener = nodeSetListener;
    }
}