View Javadoc

1   /*
2    * $Id: OASTEvent.java,v 1.19 2005/08/12 15:57:06 jlerner Exp $
3    *
4    * Copyright (c) 1999-2004, BBN Technologies, LLC.
5    * All rights reserved.
6    * http://www.daml.org/legal/opensource/bbn_license.html
7    */
8    
9   package com.bbn.swede.core.dom;
10  
11  import java.util.ArrayList;
12  import java.util.Iterator;
13  import java.util.List;
14  import java.util.Stack;
15  
16  /***
17   * Represents the addition, removal, or replacement of nodes in an OAST.
18   * @author jlerner
19   */
20  public class OASTEvent
21  {
22     /***
23      * Provides a reference to the node that originally caused an event
24      * and its state at the time.
25      * @author jlerner
26      */
27     public class OASTNodeRef
28     {
29        private OASTNode _nodeOriginal;
30        private int _iOffset;
31        private int _iLength;
32        private String _sText;
33  
34        /***
35         * Creates a new reference for a node.
36         * @param node The node
37         */
38        public OASTNodeRef(OASTNode node)
39        {
40           _nodeOriginal = node;
41           _iOffset = node.getOffset();
42           _iLength = node.getLength();
43           _sText = node.generateNodeText();
44        }
45        
46        /***
47         * Returns the offset of the node at the time the event was caused.
48         * @return The node's offset
49         */
50        public int getOffset()
51        {
52           return _iOffset;
53        }
54        
55        /***
56         * Returns the length of the node at the time the event was caused.
57         * @return The node's length
58         */
59        public int getLength()
60        {
61           return _iLength;
62        }
63        
64        /***
65         * Returns the generated text of the node at the time the event was caused.
66         * @return The node's generated text
67         */
68        public String getText()
69        {
70           return _sText;
71        }
72        
73        /***
74         * Returns a reference to the original node involved in the event.  This
75         * nodes offset, length, and generated text may not correspond to those
76         * stored in the event reference due to additional events merged with
77         * this one.
78         * @return The node that originally caused the event
79         */
80        public OASTNode getNode()
81        {
82           return _nodeOriginal;
83        }
84     }
85     
86     private ArrayList _alRemoved = new ArrayList();
87     private ArrayList _alInserted = new ArrayList();
88     private IOASTDelta _delta;
89     private boolean _bTextEvent;
90     private IOWLAbstractSyntaxTree _tree;
91     private OASTNode _nodePrimary;
92     
93     /***
94      * Creates a deep copy of an OAST event.
95      * @param other The event to clone
96      */
97     public OASTEvent(OASTEvent other)
98     {
99        _alRemoved = new ArrayList(other._alRemoved);
100       _alInserted = new ArrayList(other._alInserted);
101       _delta = new OASTDelta(other._delta);
102       _bTextEvent = other._bTextEvent;
103       _tree = other._tree;
104       _nodePrimary = other._nodePrimary;
105    }
106    
107    /***
108     * Constructs a new OASTEvent representing a specific set of additions and
109     * removals.  A reparse delta will be created for the event.  Both the
110     * <code>removed</code> and <code>inserted</code> arrays must contain at
111     * least one element.
112     * @deprecated As of SWeDE 2.0.0, use {@link #OASTEvent(IOASTDelta)} instead.
113     * @param removed Array of nodes being removed
114     * @param inserted Array of nodes being inserted
115     */
116    public OASTEvent(OASTNode[] removed, OASTNode[] inserted)
117    {
118       OASTNodeRef[] refRemoved = new OASTNodeRef[removed.length];
119       for (int i = 0; i < removed.length; i++)
120       {
121          refRemoved[i] = new OASTNodeRef(removed[i]);
122       }
123       OASTNodeRef[] refInserted = new OASTNodeRef[inserted.length];
124       for(int i = 0; i < inserted.length; i++)
125       {
126          refInserted[i] = new OASTNodeRef(inserted[i]);
127       }
128       _alRemoved.add(refRemoved);
129       _alInserted.add(refInserted);
130       _delta = OASTDelta.createReparseDelta(removed, inserted);
131       _tree = _delta.getNodeAfter().getOWLAbstractSyntaxTree();
132       ((OASTDelta)_delta).dump();
133    }
134    
135    /***
136     * Constructs a new OASTEvent representing a change signified by an OAST
137     * delta.  The lists of removed and inserted notes are created based on
138     * the delta for compatibility with listeners written for the original
139     * event system.
140     * @param delta the OAST delta representing the change
141     */
142    public OASTEvent(IOASTDelta delta)
143    {
144       _delta = delta;
145       
146       //This is safe because the delta tree is always rooted at a DESCENDENT
147       //delta for the DocumentRoot
148       _tree = _delta.getNodeAfter().getOWLAbstractSyntaxTree();
149       
150       final ArrayList alr = new ArrayList();
151       final ArrayList ali = new ArrayList();
152       delta.accept(new IOASTDeltaVisitor()
153       {
154          private static final int MASK_REMOVED  = 
155             IOASTDelta.REMOVED  | IOASTDelta.CHANGED | IOASTDelta.REPLACED;
156          private static final int MASK_INSERTED = 
157             IOASTDelta.INSERTED | IOASTDelta.CHANGED | IOASTDelta.REPLACED;
158 
159          /* (non-Javadoc)
160           * @see com.bbn.swede.core.dom.IOASTDeltaVisitor#visit(com.bbn.swede.core.dom.IOASTDelta)
161           */
162          public boolean visit(IOASTDelta delta)
163          {
164             if ((delta.getType() & MASK_REMOVED) > 0)
165             {
166                alr.add(delta.getNodeBefore());
167             }
168             if ((delta.getType() & MASK_INSERTED) > 0)
169             {
170                ali.add(delta.getNodeAfter());
171             }
172             return true;
173          }
174       });
175       
176       //filter out all but top-level removes and inserts
177       for (int i = alr.size() - 1; i >= 0; i--)
178       {
179          OASTNode node = (OASTNode)alr.get(i);
180          if (alr.contains(node.getParent()))
181          {
182             alr.remove(i);
183          }
184       }
185       for (int i = ali.size() - 1; i >= 0; i--)
186       {
187          OASTNode node = (OASTNode)ali.get(i);
188          if (ali.contains(node.getParent()))
189          {
190             ali.remove(i);
191          }
192       }
193 
194       //Removed nodes must be stored as references to the actual node that
195       //used to exist in the OAST.  This allows the OAST's to properly apply
196       //events that are created internally due to a reparse.  This is safe
197       //because a removed node cannot be further modified in a later
198       //event that gets merged with this one
199       OASTNodeRef[] refRemoved = new OASTNodeRef[alr.size()];
200       Iterator it = alr.iterator();
201       int i = 0;
202       while (it.hasNext())
203       {
204          OASTNode node = (OASTNode)it.next();
205          refRemoved[i] = new OASTNodeRef(node);
206          i++;
207       }
208       OASTNodeRef[] refInserted = new OASTNodeRef[ali.size()];
209       it = ali.iterator();
210       i = 0;
211       while (it.hasNext())
212       {
213          OASTNode node = (OASTNode)it.next();
214          refInserted[i] = new OASTNodeRef(node);
215          i++;
216       }
217       _alRemoved.add(refRemoved);
218       _alInserted.add(refInserted);
219       
220 //      ((OASTDelta)delta).dump();
221 //      OWLCore.trace("Event","--removed---",false);
222 //      for (int i = 0; i < _anodeRemoved.length; i++)
223 //      {
224 //         OWLCore.trace("Event",_anodeRemoved[i].toString(),false);
225 //      }
226 //      OWLCore.trace("Event","--inserted--",false);
227 //      for (int i = 0; i < _anodeInserted.length; i++)
228 //      {
229 //         OWLCore.trace("Event",_anodeInserted[i].toString(),false);
230 //      }
231    }
232    
233    /***
234     * Indicates if this event represents a node removal.  Note that isRemove()
235     * and isInsert() are not mutually exclusive.
236     * @return true if this event indicates one or more nodes being removed,
237     *         false if not
238     */
239    public boolean isRemove()
240    {
241       Iterator it = _alRemoved.iterator();
242       while (it.hasNext())
243       {
244          OASTNodeRef[] removed = (OASTNodeRef[])it.next();
245          if (removed.length > 0)
246          {
247             return true;
248          }
249       }
250       return false;
251    }
252    
253    /***
254     * Indicates if this event represents a node insertion.  Note that
255     * isInsert() and isRemove() are not mutually exclusive.
256     * @return true if this event indicates one or more nodes being inserted,
257     *         false if not
258     */
259    public boolean isInsert()
260    {
261       Iterator it = _alInserted.iterator();
262       while (it.hasNext())
263       {
264          OASTNodeRef[] inserted = (OASTNodeRef[])it.next();
265          if (inserted.length > 0)
266          {
267             return true;
268          }
269       }
270       return false;
271    }
272    
273    /***
274     * Indicates if this event represents a node replacement.  This is a
275     * convenience method, fully equivalent to isRemove() && isInsert()
276     * @return true if this event indiciates both a removal and an insertion of
277     *         nodes, false if it is only one or the other.
278     */
279    public boolean isReplace()
280    {
281       return (isRemove() && isInsert());
282    }
283    
284    /***
285     * Retrieves the OWL abstract syntax tree affected by this event.
286     * @return The affected OAST
287     */
288    public IOWLAbstractSyntaxTree getOWLAbstractSyntaxTree()
289    {
290       return _tree;
291    }
292    
293    /***
294     * Retrieves the node(s) being removed.  This may be an empty array.
295     * @return The array of nodes being removed
296     * @deprecated As of SWeDE 2.0.0, use {@link #getCount()}, 
297     *             {@link #getRemoved(int)}, and {@link #getInserted(int)} 
298     *             instead.  These methods allow you to properly capture the
299     *             sequence of top-level removes and inserts represented by
300     *             this events.  If this sequence of events is unimportant,
301     *             considering using {@link #getDelta()} instead.  
302     */
303    public OASTNode[] getRemoved()
304    {
305       DeltaCollector dc = new DeltaCollector(IOASTDelta.REMOVED | IOASTDelta.CHANGED | IOASTDelta.REPLACED);
306       getDelta().accept(dc);
307       List l = dc.getDeltas();
308       OASTNode[] removed = new OASTNode[l.size()];
309       for (int i = 0; i < l.size(); i++)
310       {
311          removed[i] = ((IOASTDelta)l.get(i)).getNodeBefore();
312       }
313       return removed;
314    }
315    
316    /***
317     * Retrieves the node(s) being inserted.  This may be an empty array.
318     * @return The array of nodes being inserted
319     * @deprecated As of SWeDE 2.0.0, use {@link #getCount()}, 
320     *             {@link #getRemoved(int)}, and {@link #getInserted(int)} 
321     *             instead.  These methods allow you to properly capture the
322     *             sequence of top-level removes and inserts represented by
323     *             this event.  If this sequence of events is unimportant,
324     *             considering using {@link #getDelta()} instead.  
325     */
326    public OASTNode[] getInserted()
327    {
328       DeltaCollector dc = new DeltaCollector(IOASTDelta.INSERTED | IOASTDelta.CHANGED | IOASTDelta.REPLACED);
329       getDelta().accept(dc);
330       List l = dc.getDeltas();
331       OASTNode[] inserted = new OASTNode[l.size()];
332       for (int i = 0; i < l.size(); i++)
333       {
334          inserted[i] = ((IOASTDelta)l.get(i)).getNodeAfter();
335       }
336       return inserted;
337    }
338    
339    /***
340     * Returns the number of individual events represented by this object.  If
341     * this is not a composite event, the count will be 1.
342     * @return The number of individual events included in this composite.
343     */
344    public int getCount()
345    {
346       return _alRemoved.size();  //size of _alInserted should always match
347    }
348    
349    /***
350     * Retrieves the removed nodes array for a specific discrete event 
351     * encapsulated by this composite event.
352     * @param iEvent The event index of the removed array to retrieve.  Must be
353     *               nonnegative and less than getCount().
354     * @return The requested event's removed node array.
355     */
356    public OASTNodeRef[] getRemoved(int iEvent)
357    {
358       return (OASTNodeRef[])_alRemoved.get(iEvent);
359    }
360    
361    /***
362     * Retrieves the inserted nodes array for a specific discrete event 
363     * encapsulated by this composite event.
364     * @param iEvent The event index of the inserted array to retrieve.  Must be
365     *               nonnegative and less than getCount().
366     * @return The requested event's inserted node array.
367     */
368    public OASTNodeRef[] getInserted(int iEvent)
369    {
370       return (OASTNodeRef[])_alInserted.get(iEvent);
371    }
372    
373    /***
374     * Retrieves the OAST delta containing detailed information about the
375     * changes involved in this event.  This will always be a DESCENDANT delta
376     * representing the OAST's DocumentRoot node, with specific node changes
377     * represented by a tree of deltas below it.
378     * @return the event's OAST delta
379     */
380    public IOASTDelta getDelta()
381    {
382       return _delta;
383    }
384    
385    /***
386     * Sets whether or not this is a text event.  By default, events are assumed
387     * not to be text events.
388     * @param bTextEvent <code>true</code> to mark this as a text event,
389     *                   <code>false</code> to mark this as a non-text event.
390     */
391    /*package*/ void setTextEvent(boolean bTextEvent)/package-summary/html">class="comment">package*/ void setTextEvent(boolean bTextEvent)/package-summary.html">/*package*/ void setTextEvent(boolean bTextEvent)/package-summary.html">class="comment">package*/ void setTextEvent(boolean bTextEvent)
392    {
393       _bTextEvent = bTextEvent;
394    }
395 
396    /***
397     * <p>Indicates whether or not this is a text event.  A text event is one that
398     * resulted from parsing text in the document.  Non-text events result from
399     * using the OASTNode interface to manipulate nodes in memory.</p>
400     * 
401     * <p>This flag is intended primarily for use by the OWL editor, which must 
402     * distinguish between text and non-text events to correctly update the
403     * document when the OAST is modified indirectly.</p>
404     * @return <code>true</code> if this is a text event, <code>false</code> if
405     *         it is a non-text event.
406     */
407    public boolean isTextEvent()
408    {
409       return _bTextEvent;
410    }
411    
412    /***
413     * <p>Turns this event into a composite event.  The event being merged with
414     * this one is assumed to have occured later, and may be either a standalone
415     * event or a composite one.  The inserted and removed arrays from the second
416     * event will be appended to the insert and remove lists of this one.  This
417     * event's delta will be updated to reflect the composite change represented
418     * by both events.</p>
419     * 
420     * <p>Events should only be combined internally by an OWL abstract syntax
421     * tree during pre-event processing and while dealing with an OAST runnable.
422     * </p>
423     * @param other The new event to merge into this one.
424     */
425    /*package*/ void combine(OASTEvent other)/package-summary/html">class="comment">package*/ void combine(OASTEvent other)/package-summary.html">/*package*/ void combine(OASTEvent other)/package-summary.html">class="comment">package*/ void combine(OASTEvent other)
426    {
427       _alRemoved.addAll(other._alRemoved);
428       _alInserted.addAll(other._alInserted);
429       
430       //make sure the primary node stays current.
431       if (_nodePrimary != null)
432       {
433          IOASTDelta delta = other.getDelta(_nodePrimary);
434          if (delta != null)
435          {
436             if (delta.typeMatches(IOASTDelta.REMOVED))
437             {
438                _nodePrimary = null;
439             }
440             else if (delta.typeMatches(IOASTDelta.CHANGED | IOASTDelta.REPLACED))
441             {
442                _nodePrimary = delta.getNodeAfter();
443             }
444          }
445       }
446 
447       ((OASTDelta)_delta).combine((OASTDelta)other._delta);
448       
449    }
450    
451    /***
452     * Searches for a delta indicating a change in a specific OAST node.
453     * @author jlerner
454     */
455    protected class NodeLocationVisitor implements IOASTDeltaVisitor
456    {
457       private OASTNode _node;
458       private IOASTDelta _delta;
459       
460       /***
461        * Creates a visitor to search for a delta matching a specific node.
462        * @param node The node to match
463        */
464       public NodeLocationVisitor(OASTNode node)
465       {
466          _node = node;
467       }
468 
469       /* (non-Javadoc)
470        * @see com.bbn.swede.core.dom.IOASTDeltaVisitor#visit(com.bbn.swede.core.dom.IOASTDelta)
471        */
472       public boolean visit(IOASTDelta delta)
473       {
474          if (delta.getNodeBefore() == _node)
475          {
476             _delta = delta;
477          }
478          
479          return (_delta == null);
480       }
481       
482       /***
483        * Retrieves the delta that matched the node.
484        * @return The matching delta, or <code>null</code> if no match was found.
485        */
486       public IOASTDelta getDelta()
487       {
488          return _delta;
489       }
490    }
491    /***
492     * Searches the delta tree for a delta representing a change in a specific
493     * OAST node.
494     * @param node The node that existed in the OAST prior to the event
495     * @return A delta indicating the change to <code>node</code>, or
496     *         <code>null</code> if node is unchanged.
497     */
498    public IOASTDelta getDelta(OASTNode node)
499    {
500       if (node == null)
501       {
502          return null;
503       }
504       
505       if (node.getOWLAbstractSyntaxTree() != null)
506       {
507          //Use the parallel between the OAST and the delta to find the
508          //change in node directly
509          
510          //Trace the node's lineage up the OAST
511          Stack sLineage = new Stack();
512          sLineage.push(node);
513          OASTNode parent = node.getParent();
514          while (parent != null)
515          {
516             sLineage.push(parent);
517             parent = parent.getParent();
518          }
519          
520          //Use the lineage stack to find the appropriate delta directly
521          IOASTDelta d = getDelta();
522          IOASTDelta[] deltas = new IOASTDelta[]{d};
523          while (sLineage.size() > 0)
524          {
525             if (deltas.length == 0)
526             {
527                return null;
528             }
529             OASTNode n = (OASTNode)sLineage.pop();
530             d = findMatchingDelta(deltas, n);
531             if (d == null)
532             {
533                return null;
534             }
535             deltas = d.getChildren();
536          }
537          
538          return d;
539       }
540       else
541       {
542          //node isn't attached to a tree so we must use a visitor to locate it
543          NodeLocationVisitor nlv = new NodeLocationVisitor(node);
544          getDelta().accept(nlv);
545          return nlv.getDelta();
546       }
547    }
548    
549    private static final int AFTER_MASK = IOASTDelta.DESCENDANT | IOASTDelta.INSERTED;
550    private IOASTDelta findMatchingDelta(IOASTDelta[] deltas, OASTNode node)
551    {
552       for (int i = 0; i < deltas.length; i++)
553       {
554          if (deltas[i].typeMatches(AFTER_MASK)
555              && deltas[i].getNodeAfter() == node)
556          {
557             return deltas[i];
558          }
559          else if (deltas[i].getNodeBefore() == node)
560          {
561             return deltas[i];
562          }
563       }
564       return null;
565    }
566 
567    /***
568     * <p>Marks a node as the primary focus of the event.  The primary node
569     * is considered to be the main purpose of a composite event, even though it
570     * may include changes to other parts of the document structure.  For 
571     * example, you may use a composite event to update the namespace 
572     * declarations to include some additional URIs and then add a tag structure
573     * to the document that uses the inserted namespaces.  Setting the tag
574     * structure as the primary node allows SWeDE to correctly treat it as the
575     * focus of the event.</p>
576     * 
577     * <p>Setting a primary node for an event is optional, and different OAST
578     * change listeners may react differently to the presence of one.  The OWL
579     * source editor, for example, checks for and selects the primary node after 
580     * formatting the text inserted by the event.</p>
581     * 
582     * <p>The primary node will be updated automatically if it is changed, 
583     * replaced, or removed by another event that is combined with this one.</p>
584     * @param node The primary node for the event.  This node must have a
585     *             corresponding delta in this event.
586     */
587    /*package*/ void setPrimaryNode(OASTNode node)/package-summary/html">class="comment">package*/ void setPrimaryNode(OASTNode node)/package-summary.html">/*package*/ void setPrimaryNode(OASTNode node)/package-summary.html">class="comment">package*/ void setPrimaryNode(OASTNode node)
588    {
589       if (node == null)
590       {
591          _nodePrimary = null;
592       }
593       IOASTDelta delta = getDelta(node);
594       if (delta != null)
595       {
596          _nodePrimary = node;
597       }
598       else
599       {
600          _nodePrimary = null;
601       }
602    }
603    
604    /***
605     * <p>Retrieves the node that is the primary focus of this event.  The primary 
606     * node is considered to be the main purpose of a composite event, even 
607     * though it may include changes to other parts of the document structure.  
608     * For example, in a composite event that update the namespace 
609     * declarations to include some additional URIs and then adds a tag structure
610     * to the document that uses the inserted namespaces, the inserted tag
611     * structure might be marked as primary so that it can correctly be treated
612     * as the focus of the event.</p>
613     * 
614     * <p>Not all events will have a primary node.  OAST change listeners should
615     * generally process the entire event delta and use the primary node only
616     * for UI cues.  For example, the OWL editor checks for a primary node and
617     * selects its text after formatting the text inserted by an API event.</p>
618     * @return The primary node for the event, or <code>null</code> if the
619     *         event has no primary node.
620     */
621    public OASTNode getPrimaryNode()
622    {
623       return _nodePrimary;
624    }
625 }