/** * 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.path; import jlibs.xml.sax.dog.NodeType; import jlibs.xml.sax.dog.path.tests.NamespaceURI; import jlibs.xml.sax.dog.path.tests.QName; import jlibs.xml.sax.dog.sniff.Event; /** * @author Santhosh Kumar T */ public final class EventID{ private final int type; public String location; public EventID previous; public boolean interestedInAttributes; public boolean interestedInNamespaces; public int interestedInText; private ConstraintEntry listenersArray[][]; public EventID(int type, ConstraintEntry listenersArray[][]){ this.type = type; this.listenersArray = listenersArray; if(type!=NodeType.DOCUMENT) rootElementVisited = true; } /*-------------------------------------------------[ Empty ]---------------------------------------------------*/ /** * empty[axis][nodetype] is true if the axis on that nodetype returns nothing */ private static boolean empty[/*axis-type*/][/*node-type*/] = new boolean[Axis.MAX+1][NodeType.MAX+1]; static{ // attribute and namespace axis makes sense only for element boolean attr[] = empty[Axis.ATTRIBUTE]; boolean namespace[] = empty[Axis.NAMESPACE]; for(int i=13; i>0; i--){ if(i!=NodeType.ELEMENT){ attr[i] = true; namespace[i] = true; } } // child and descendant axis makes sense only for document and element boolean child[] = empty[Axis.CHILD]; boolean descendant[] = empty[Axis.DESCENDANT]; for(int i=13; i>0; i--){ if(i!=NodeType.DOCUMENT && i!=NodeType.ELEMENT){ child[i] = true; descendant[i] = true; } } // following-sibling axis doesn't make sense for document, attribute and namespace boolean followingSibling[] = empty[Axis.FOLLOWING_SIBLING]; followingSibling[NodeType.DOCUMENT] = true; followingSibling[NodeType.ATTRIBUTE] = true; followingSibling[NodeType.NAMESPACE] = true; // following axis doesn't make sense for document empty[Axis.FOLLOWING][NodeType.DOCUMENT] = true; } /** tells if the specified axis always returns empty on this eventID */ public boolean isEmpty(int axis){ return empty[axis][type]; } /*-------------------------------------------------[ Listeners ]---------------------------------------------------*/ private static class AxisEntry{ boolean active; int textCount; ConstraintEntry constraintEntry; public AxisEntry(boolean active){ this.active = active; } } public static class ConstraintEntry{ Constraint constraint; AxisListener listener; ConstraintEntry next; public ConstraintEntry(Constraint constraint, AxisListener listener){ this.constraint = constraint; this.listener = listener; } } private int activeCount; public int axisEntryCount; private AxisEntry axisEntries[] = new AxisEntry[6]; private boolean checkState(){ if(axisEntries[Axis.ATTRIBUTE]!=null) assert axisEntries[Axis.ATTRIBUTE].active; if(axisEntries[Axis.NAMESPACE]!=null) assert axisEntries[Axis.NAMESPACE].active; int active = activeCount; int total = axisEntryCount; int textCount = 0; for(AxisEntry entry: axisEntries){ if(entry!=null){ assert entry.constraintEntry!=null; if(entry.active){ active--; for(ConstraintEntry constraintEntry=entry.constraintEntry; constraintEntry!=null; constraintEntry=constraintEntry.next){ int constraintID = constraintEntry.constraint.id; if(constraintID==Constraint.ID_NODE || constraintID==Constraint.ID_TEXT) textCount++; } } total--; assert active>=0; } } assert active==0 && total==0; assert interestedInText==textCount; return true; } public void addListener(Event event, Step step, AxisListener listener){ int axis = step.axis; assert !isEmpty(axis); Constraint constraint = step.constraint; if(axis==Axis.SELF){ if(constraint.matches(event)) listener.onHit(this); listener.expired(); return; } if(axis==Axis.DESCENDANT_OR_SELF){ if(constraint.matches(event)){ listener.onHit(this); if(listener.manuallyExpired) return; } axis = Axis.DESCENDANT; } boolean active = true; int constraintID = constraint.id; switch(axis){ case Axis.CHILD: if(type==NodeType.DOCUMENT && constraintID==Constraint.ID_TEXT){ listener.expired(); return; } break; case Axis.FOLLOWING_SIBLING: case Axis.FOLLOWING: if(type==NodeType.ELEMENT) active = false; break; } AxisEntry entry = axisEntries[axis]; if(entry==null){ axisEntries[axis] = entry = new AxisEntry(active); if(active) ++activeCount; axisEntryCount++; } ConstraintEntry listeners[] = listenersArray[axis]; ConstraintEntry oldConstraintEntry = listeners[constraintID]; if(oldConstraintEntry!=null){ assert entry.constraintEntry!=null; listener.nextAxisListener = oldConstraintEntry.listener; oldConstraintEntry.listener = listener; }else{ ConstraintEntry constraintEntry = new ConstraintEntry(constraint, listener); constraintEntry.next = entry.constraintEntry; entry.constraintEntry = constraintEntry; listeners[constraintID] = constraintEntry; if(constraintID==Constraint.ID_NODE || constraintID==Constraint.ID_TEXT){ entry.textCount++; if(active) interestedInText++; } } assert checkState(); } public void listenersAdded(){ int noOfConstraints = listenersArray[0].length; // iterating axises other than Descendant-Self, Self for(int iAxis=0; iAxis<Axis.MAX_TRACKED+1; iAxis++){ ConstraintEntry listeners[] = listenersArray[iAxis]; for(int iListener=0; iListener<noOfConstraints; iListener++) listeners[iListener] = null; } interestedInNamespaces = axisEntries[Axis.NAMESPACE]!=null; interestedInAttributes = axisEntries[Axis.ATTRIBUTE]!=null; } private void expire(int axis){ AxisEntry axisEntry = axisEntries[axis]; if(axisEntry!=null){ assert axisEntry.active; ConstraintEntry constraintEntry = axisEntry.constraintEntry; do{ expireList(constraintEntry.listener); constraintEntry = constraintEntry.next; }while(constraintEntry!=null); interestedInText -= axisEntry.textCount; axisEntries[axis] = null; activeCount--; axisEntryCount--; } } private void expireList(AxisListener listener){ do{ if(!listener.manuallyExpired) listener.expired(); AxisListener next = listener.nextAxisListener; listener.nextAxisListener = null; listener = next; }while(listener!=null); } private void inactivate(int axis){ AxisEntry axisEntry = axisEntries[axis]; if(axisEntry!=null && axisEntry.active){ axisEntry.active = false; interestedInText -= axisEntry.textCount; activeCount--; } } private void activate(int axis){ AxisEntry axisEntry = axisEntries[axis]; if(axisEntry!=null && !axisEntry.active){ axisEntry.active = true; interestedInText += axisEntry.textCount; activeCount++; } } /*-------------------------------------------------[ Axis Matching ]---------------------------------------------------*/ private int d; public boolean push(){ assert axisEntryCount!=0; d++; switch(d){ case 1: inactivate(Axis.FOLLOWING_SIBLING); break; case 2: if(interestedInNamespaces){ expire(Axis.NAMESPACE); interestedInNamespaces = false; } if(interestedInAttributes){ expire(Axis.ATTRIBUTE); interestedInAttributes = false; } inactivate(Axis.CHILD); } assert checkState(); return axisEntryCount==0; } private boolean subTreeFinished = false; private boolean parentLevelFinished = false; public boolean pop(boolean doc){ assert axisEntryCount!=0; d--; if(doc) expire(Axis.FOLLOWING); switch(d){ case -1: parentLevelFinished = true; expire(Axis.FOLLOWING_SIBLING); break; case 0: if(!subTreeFinished){ subTreeFinished = true; if(interestedInNamespaces){ expire(Axis.NAMESPACE); interestedInNamespaces = false; } if(interestedInAttributes){ expire(Axis.ATTRIBUTE); interestedInAttributes = false; } expire(Axis.CHILD); expire(Axis.DESCENDANT); } if(!parentLevelFinished){ activate(Axis.FOLLOWING_SIBLING); activate(Axis.FOLLOWING); } break; case 1: if(!subTreeFinished) activate(Axis.CHILD); break; } assert checkState(); return axisEntryCount==0; } private boolean rootElementVisited; public boolean onEvent(Event event){ assert axisEntryCount!=0; if(activeCount==0) return false; int eventType = event.type(); if(eventType==NodeType.NAMESPACE){ if(interestedInNamespaces){ if(onEvent(event, axisEntries[Axis.NAMESPACE])){ axisEntries[Axis.NAMESPACE] = null; activeCount--; axisEntryCount--; interestedInNamespaces = false; } } }else{ if(interestedInNamespaces){ expire(Axis.NAMESPACE); interestedInNamespaces = false; } if(eventType==NodeType.ATTRIBUTE){ if(interestedInAttributes){ if(onEvent(event, axisEntries[Axis.ATTRIBUTE])){ axisEntries[Axis.ATTRIBUTE] = null; activeCount--; axisEntryCount--; interestedInAttributes = false; } } }else{ if(interestedInAttributes){ expire(Axis.ATTRIBUTE); interestedInAttributes = false; } for(int i=2; i<6; i++){ // excluding namespace & attribute axisEntries AxisEntry axisEntry = axisEntries[i]; if(axisEntry!=null && axisEntry.active){ if(onEvent(event, axisEntry)){ axisEntries[i] = null; activeCount--; axisEntryCount--; } } } //take advantage that document has only one element if(!rootElementVisited && eventType==NodeType.ELEMENT){ rootElementVisited = true; AxisEntry childEntry = axisEntries[Axis.CHILD]; if(childEntry!=null){ assert childEntry.active; ConstraintEntry headConstraintEntry = null; ConstraintEntry lastConstraintEntry = null; ConstraintEntry constraintEntry = childEntry.constraintEntry; do{ Constraint constraint = constraintEntry.constraint; int constraintID = constraint.id; if(constraintID==Constraint.ID_STAR || constraintID==Constraint.ID_PARENTNODE || constraintID==Constraint.ID_ELEMENT || constraint instanceof NamespaceURI || constraint instanceof QName){ expireList(constraintEntry.listener); }else{ if(headConstraintEntry==null) headConstraintEntry = constraintEntry; else lastConstraintEntry.next = constraintEntry; lastConstraintEntry = constraintEntry; } constraintEntry = constraintEntry.next; }while(constraintEntry!=null); childEntry.constraintEntry = headConstraintEntry; if(lastConstraintEntry!=null) lastConstraintEntry.next = null; if(headConstraintEntry==null){ axisEntries[Axis.CHILD] = null; activeCount--; axisEntryCount--; } } } } } assert checkState(); return axisEntryCount==0; } private boolean onEvent(Event event, AxisEntry axisEntry){ assert axisEntry.active; EventID eventID = null; ConstraintEntry headConstraintEntry = null; ConstraintEntry lastConstraintEntry = null; ConstraintEntry constraintEntry = axisEntry.constraintEntry; do{ Constraint constraint = constraintEntry.constraint; boolean keep = true; if(constraint.matches(event)){ AxisListener headListener = null; AxisListener lastListener = null; AxisListener listener = constraintEntry.listener; do{ if(!listener.manuallyExpired){ if(eventID==null) eventID = event.getID(); listener.onHit(eventID); if(!listener.manuallyExpired){ if(headListener==null) headListener = listener; else lastListener.nextAxisListener = listener; lastListener = listener; } } listener = listener.nextAxisListener; }while(listener!=null); if(headListener==null){ keep = false; int constraintID = constraint.id; if(constraintID==Constraint.ID_NODE || constraintID==Constraint.ID_TEXT){ axisEntry.textCount--; interestedInText--; } }else{ constraintEntry.listener = headListener; lastListener.nextAxisListener = null; } } if(keep){ if(headConstraintEntry==null) headConstraintEntry = constraintEntry; else lastConstraintEntry.next = constraintEntry; lastConstraintEntry = constraintEntry; } constraintEntry = constraintEntry.next; }while(constraintEntry!=null); if(headConstraintEntry==null) return true; else{ axisEntry.constraintEntry = headConstraintEntry; lastConstraintEntry.next = null; return false; } } }