1
2
3
4
5
6
7
8
9 package com.bbn.swede.core.dom;
10
11 import java.io.BufferedInputStream;
12 import java.io.IOException;
13 import java.io.StringReader;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collections;
17 import java.util.Comparator;
18 import java.util.Hashtable;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.StringTokenizer;
23
24 import javax.xml.parsers.FactoryConfigurationError;
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.parsers.SAXParser;
27 import javax.xml.parsers.SAXParserFactory;
28
29 import org.eclipse.core.resources.IFile;
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.QualifiedName;
33 import org.eclipse.jface.text.IRegion;
34 import org.eclipse.jface.text.Region;
35 import org.eclipse.ui.PartInitException;
36 import org.xml.sax.InputSource;
37 import org.xml.sax.SAXException;
38 import org.xml.sax.helpers.DefaultHandler;
39
40 import com.bbn.swede.core.IOWLDocument;
41 import com.bbn.swede.core.IOWLElement;
42 import com.bbn.swede.core.IOWLElementVisitor;
43 import com.bbn.swede.core.IOWLProject;
44 import com.bbn.swede.core.OWLCore;
45 import com.bbn.swede.core.dom.OASTEvent.OASTNodeRef;
46 import com.bbn.swede.core.dom.rdf.Rdf;
47 import com.bbn.swede.core.resources.SWResourceManager;
48 import com.hp.hpl.jena.ontology.OntModel;
49 import com.hp.hpl.jena.ontology.OntModelSpec;
50 import com.hp.hpl.jena.rdf.model.ModelFactory;
51 import com.hp.hpl.jena.rdf.model.Resource;
52 import com.hp.hpl.jena.rdf.model.Statement;
53
54 /***
55 * This implementation of {@link com.bbn.swede.core.dom.IOWLAbstractSyntaxTree}
56 * manages the actual OASTNode structure of the tree. OAST is responsible for
57 * parsing (both full and partial), node insertion, removal, and replacement,
58 * sending notifications to OAST change listeners, and adapting the tree into a
59 * Jena model.
60 * @author jlerner
61 */
62 public class OAST implements IOWLAbstractSyntaxTree
63 {
64 private static final String S_UNPARSEABLE = "com.bbn.swede.ui.unparseablemarker";
65 private static final QualifiedName QNAME_UNPARSEABLE =
66 new QualifiedName("com.bbn.swede.core", "unparseable");
67
68 private DocumentRoot _root;
69 private ArrayList _alListeners = new ArrayList();
70 private ArrayList _alPrenotifiedListeners = new ArrayList();
71 private IFile _file;
72 private OntModel _model;
73 private Map _mapResourceToNode;
74 private Map _mapStatementToNode;
75
76 /***
77 * Returns the root node of the OAST.
78 * @return The root node.
79 */
80 public DocumentRoot getRoot()
81 {
82 return _root;
83 }
84
85 /***
86 * Translates full URIs into qualified names based on the namespace
87 * abbreviations defined within the document.
88 * @param uri The URI to translate
89 * @return If a namespace abbreviation can be found for the URI, or it
90 * exists in the default namespace, a qualified name for the URI.
91 * Otherwise, the URI itself is returned.
92 */
93 public String getQNameForURI(String uri)
94 {
95 String toReturn = uri;
96 OASTNode[] namespaceNodes = null;
97 String temp = null;
98 Namespace namespace = null;
99 String fragment = null;
100 String baseURI = null;
101
102 if(uri.indexOf("#") != -1)
103 {
104 fragment = uri.substring(uri.lastIndexOf("#") + 1);
105 baseURI = uri.substring(0, uri.lastIndexOf("#") + 1);
106 namespaceNodes = _root.getNodesOfType(OASTNode.NAMESPACE);
107 for (int i = 0; i < namespaceNodes.length; i++)
108 {
109 namespace = (Namespace)namespaceNodes[i];
110 if(baseURI.equals(getBaseURI() + "#"))
111 {
112 toReturn = fragment;
113 break;
114 }
115 else
116 {
117 if(namespace.getValue().equals(baseURI))
118 {
119 temp = namespace.getQName();
120 if(temp.lastIndexOf(":") != -1)
121 {
122 temp = temp.substring(temp.lastIndexOf(":") + 1);
123 toReturn = temp + ":" + fragment;
124 }
125 else
126 {
127 toReturn = fragment;
128 }
129 break;
130 }
131 }
132 }
133 }
134
135 return toReturn;
136 }
137
138 /***
139 * Translates qualified names into full URIs based on the namespace
140 * abbreviations found within the document.
141 * @param qName The qualified name to translate
142 * @return If the namespace abbreviation in the specified qname exists, or
143 * if it refers to the default namespace, the full URI. Otherwise,
144 * the qualified name itself is returned.
145 */
146 public String getURIForQName(String qName)
147 {
148 String toReturn = qName;
149 OASTNode[] namespaceNodes = null;
150 String temp = null;
151 Namespace namespace = null;
152 String fragment = null;
153 String baseURI = null;
154
155 if(qName.indexOf(":") != -1)
156 {
157 fragment = qName.substring(qName.lastIndexOf(":") + 1);
158 baseURI = qName.substring(0, qName.lastIndexOf(":") + 1);
159 namespaceNodes = _root.getNodesOfType(OASTNode.NAMESPACE);
160 for (int i = 0; i < namespaceNodes.length; i++)
161 {
162 namespace = (Namespace)namespaceNodes[i];
163 temp = namespace.getQName();
164 temp = temp.substring(temp.lastIndexOf(":") + 1);
165 if(temp.equals(baseURI))
166 {
167 toReturn = namespace.getValue() + "#" + fragment;
168 break;
169 }
170 }
171 }
172 if(qName.startsWith("#"))
173 {
174 toReturn = getBaseURI() + qName;
175 }
176
177 return toReturn;
178 }
179
180 /***
181 * Produces a list of unparseable regions in the document, as it is currently
182 * stored in the project metadata. For information on unparseability in
183 * the current, possibly unsaved, document state, use
184 * getNodesOfType(OASTNode.UNPARSEABLE)
185 * @return An array of IRegions representing all the unparseable sections
186 * of the document
187 */
188 public IRegion[] getUnparseableRegions()
189 {
190 String s = null;
191 try
192 {
193 s = _file.getPersistentProperty(QNAME_UNPARSEABLE);
194 }
195 catch (CoreException e)
196 {
197 OWLCore.logWarning(
198 OWLCore.getID(),
199 "Error retrieving persistant property",
200 e);
201 }
202 if (s == null)
203 {
204 return new IRegion[0];
205 }
206 int iOffset, iLength;
207 StringTokenizer st = new StringTokenizer(s, " ");
208 ArrayList al = new ArrayList();
209 while (st.hasMoreTokens())
210 {
211 iOffset = Integer.parseInt(st.nextToken());
212 iLength = Integer.parseInt(st.nextToken());
213 al.add(new Region(iOffset, iLength));
214 }
215 return (IRegion[]) al.toArray(new IRegion[0]);
216 }
217
218 /***
219 * Locates XML comments in a string. The comments will be blanked out
220 * in the string and returend as XMLComment objects specifying the positions
221 * where they were found.
222 * @param sb The StringBuffer to be cleansed of XML comments
223 * @return An ArrayList of positioned XMLComments with correct offsets
224 * and lengths that are not attached to the tree
225 */
226 protected ArrayList filterComments(StringBuffer sb)
227 {
228 ArrayList al = new ArrayList();
229 for (int iPos = sb.toString().indexOf("<!--");
230 iPos >= 0;
231 iPos = sb.toString().indexOf("<!--"))
232 {
233 int iEnd = sb.toString().indexOf("-->", iPos + 4);
234 if (iEnd < 0)
235 {
236 return al;
237 }
238 String s = sb.toString().substring(iPos + 4, iEnd);
239 XMLComment xc = new XMLComment(s);
240 xc.setOffset(iPos);
241 xc.setLength(iEnd - iPos + 3);
242 al.add(xc);
243 for (int i = iPos; i < iEnd + 3; i++)
244 {
245 sb.setCharAt(i, ' ');
246 }
247 }
248 return al;
249 }
250
251 /***
252 * Attaches a list of already-positioned XMLComment nodes to the OAST.
253 * @param alComments The list of XMLComments to attach
254 * @param root Root of the tree that will receive the comments
255 */
256 protected void attachComments(ArrayList alComments, OASTNode root)
257 {
258 Iterator it = alComments.iterator();
259 while (it.hasNext())
260 {
261 XMLComment xc = (XMLComment) it.next();
262
263 OASTNode node = root.getContainingChild(xc.getOffset());
264 try
265 {
266 node.insert(xc, false);
267 }
268 catch (OASTException e)
269 {
270
271
272
273 }
274 }
275 }
276
277 private class NodeLevelComparator implements Comparator
278 {
279 public int compare(Object o1, Object o2)
280 {
281 if (!(o1 instanceof UnparseableNode)
282 || !(o2 instanceof UnparseableNode))
283 {
284 return 0;
285 }
286 UnparseableNode u1 = (UnparseableNode)o1;
287 UnparseableNode u2 = (UnparseableNode)o2;
288 if (u1.getLevel() < u2.getLevel())
289 {
290 return -1;
291 }
292 else if (u1.getLevel() > u2.getLevel())
293 {
294 return 1;
295 }
296 return 0;
297 }
298
299 public boolean equals(Object arg0)
300 {
301 return false;
302 }
303 }
304
305 /***
306 * Parses an entire document into an OWL Abstract Syntax Tree. The result
307 * of the parse is returned as a dummy root node, suitable for building
308 * an OAST event.
309 * @param file The file resource to be parsed
310 * @return A dummy node whose children represent one or more node structures
311 * resulting from the parse, or <code>null</code> if the parse fails.
312 */
313 private OASTNode doParseDocument(IFile file)
314 {
315 if (!file.isDerived() && !file.isSynchronized(IResource.DEPTH_ZERO)
316 && !file.getWorkspace().isTreeLocked())
317 {
318 try
319 {
320 file.refreshLocal(IResource.DEPTH_ZERO, null);
321 }
322 catch (CoreException e1)
323 {
324 OWLCore.logWarning(OWLCore.getID(),
325 "Unable to refresh file resource.", e1);
326 }
327 }
328 SAXParser parser;
329 try
330 {
331 parser = SAXParserFactory.newInstance().newSAXParser();
332
333 IRegion[] regions = getUnparseableRegions();
334 StringBuffer sb = new StringBuffer();
335 BufferedInputStream bis =
336 new BufferedInputStream(
337 new UnparseableFilterStream(file.getContents(), regions));
338 for (int i = bis.read(); i >= 0; i = bis.read())
339 {
340 sb.append((char) i);
341 }
342 bis.close();
343 OASTNode dummy = partialParse(_root, null, sb.toString(), 0);
344 return dummy;
345 }
346 catch (Exception e)
347 {
348 OWLCore.logWarning(
349 OWLCore.getID(),
350 "Unable to parse " + file.getName(),
351 e);
352 return null;
353 }
354 }
355
356 /***
357 * Generates unparseable nodes based on document metadata and attaches them
358 * to a node tree. The tree must already have space for the nodes to be
359 * created.
360 * @param file The file represented by the node tree
361 * @param root The root of the node tree to receive the unparseable nodes
362 */
363 private void restorePersistedUnparseables(IFile file, OASTNode root)
364 {
365 IRegion[] regions = getUnparseableRegions();
366
367
368 Arrays.sort(regions, new Comparator()
369 {
370 public int compare(Object arg0, Object arg1)
371 {
372 if (arg0 instanceof IRegion && arg1 instanceof IRegion)
373 {
374 IRegion r1 = (IRegion)arg0;
375 IRegion r2 = (IRegion)arg1;
376 if (r1.getOffset() < r2.getOffset())
377 {
378 return -1;
379 }
380 else if (r1.getOffset() > r2.getOffset())
381 {
382 return 1;
383 }
384 else
385 {
386 if (r1.getLength() < r2.getLength())
387 {
388
389 return 1;
390 }
391 else if (r1.getLength() > r2.getLength())
392 {
393
394 return -1;
395 }
396 }
397 }
398 return 0;
399 }
400
401 public boolean equals(Object arg0)
402 {
403 return this.equals(arg0);
404 }
405 });
406
407 try
408 {
409
410 if(file.getRawLocation() != null)
411 {
412 UnparseableFilterRandomAccessFile ufraf =
413 new UnparseableFilterRandomAccessFile(
414 file.getRawLocation().toFile(),
415 "r",
416 regions);
417 for (int i = 0; i < regions.length; i++)
418 {
419 IRegion r = regions[i];
420 ufraf.seek(r.getOffset());
421 byte[] bytes = new byte[r.getLength()];
422 ufraf.readUnfiltered(bytes);
423 String sText = new String(bytes);
424 OASTNode node = root.getContainingChild(r.getOffset());
425 if (node != null && sText != null)
426 {
427 UnparseableNode un = new UnparseableNode(sText);
428 un.setOffset(r.getOffset());
429 un.setLength(sText.length());
430 if (!(node instanceof UnparseableNode))
431 {
432 un.createMarker(file);
433 }
434 node.insert(un, false);
435
436
437
438
439
440 }
441 }
442 ufraf.close();
443 }
444 }
445 catch (Exception e)
446 {
447 OWLCore.logWarning(
448 OWLCore.getID(),
449 "Unable to restore persisted unparseable regions in " + file.getName(),
450 e);
451 }
452 }
453
454 /***
455 * Parses an entire document into an OWL Abstract Syntax Tree. If this
456 * instance of OAST already contains a tree, it will be replaced with the
457 * results of the parse
458 * @param file File to be parsed
459 */
460 public void parseDocument(IFile file)
461 {
462 _file = file;
463 _root = new DocumentRoot(_file, this);
464 OASTNode dummy = doParseDocument(_file);
465 _root.setLength(dummy.getLength());
466 restorePersistedUnparseables(_file, dummy);
467 attachChildren(dummy, _root);
468
469 _bReadOnly = file.isReadOnly();
470 }
471
472 private boolean _bReadOnly;
473
474
475
476
477
478 public boolean isReadOnly()
479 {
480 return _bReadOnly;
481 }
482
483 /***
484 * Builds a Jena model based on the current state of the OAST. This method
485 * should only be called when something actually requests the model, as it
486 * may take some time to build it.
487 */
488 protected void createModel()
489 {
490
491 _model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM, null);
492 _mapResourceToNode = new Hashtable();
493 _mapStatementToNode = new Hashtable();
494 OASTNode[] nodes = _root.getNodesOfType(OASTNode.RDF_RDF, 1);
495 if (nodes.length == 0)
496 {
497 return;
498 }
499 OASTNode nodeRdf = nodes[0];
500 addToModel(nodeRdf);
501 }
502
503 /***
504 * Attempts to add a node to the Jena model. This means either creating a
505 * Resource (for subclasses of ClassNode) or a statement (for
506 * GenericAttribute and subclasses of PropertyNode).
507 * @param node Node to integrate into the Jena model
508 */
509 /*package*/ void addToModel(OASTNode node)/package-summary/html">class="comment">package*/ void addToModel(OASTNode node)/package-summary.html"> void addToModel(OASTNode node)
510 {
511 if (node instanceof ClassNode)
512 {
513 ClassNode cn = (ClassNode) node;
514 cn.createJenaResource(_model);
515 Resource res = cn.getAssociatedResource();
516 if (res == null)
517 {
518 return;
519 }
520
521 addResource(res, node);
522
523 Statement stmt = cn.getImplicitTypeStatement();
524 addStatement(stmt, node);
525 }
526 OASTNode[] children = node.getChildren();
527 for (int i = 0; i < children.length; i++)
528 {
529 addToModel(children[i]);
530 }
531 if (node instanceof PropertyNode)
532 {
533 PropertyNode pn = (PropertyNode) node;
534 pn.createJenaStatement(_model);
535
536 Statement stmt = pn.getAssociatedStatement();
537 addStatement(stmt, node);
538
539 Resource res = pn.getReification();
540 addResource(res, node);
541 }
542 if (node instanceof GenericAttribute)
543 {
544 GenericAttribute ga = (GenericAttribute) node;
545 ga.createJenaStatement(_model);
546
547 Statement stmt = ga.getAssociatedStatement();
548 addStatement(stmt, node);
549 }
550 }
551
552 /***
553 * Removes a node and all its descendents from the Jena model.
554 * @param node The node to remove
555 */
556 /*package*/ void removeFromModel(OASTNode node)/package-summary/html">class="comment">package*/ void removeFromModel(OASTNode node)/package-summary.html"> void removeFromModel(OASTNode node)
557 {
558 ModelCleanupVisitor mcv = new ModelCleanupVisitor(_model,
559 _mapResourceToNode, _mapStatementToNode);
560 if (node.getParent() instanceof PropertyNode)
561 {
562
563
564 node = node.getParent();
565 }
566 node.accept(mcv);
567 }
568
569 /***
570 * Adds a resource/node pair to the resource map.
571 * @param res The resource
572 * @param node The node. Must not be null.
573 */
574 private void addResource(Resource res, OASTNode node)
575 {
576 if (res == null)
577 {
578 return;
579 }
580
581 List l = (List)_mapResourceToNode.get(res);
582 if (l == null)
583 {
584 l = new ArrayList(1);
585 _mapResourceToNode.put(res, l);
586 }
587 if (!l.contains(node))
588 {
589 l.add(node);
590 }
591 }
592
593 /***
594 * Adds a statement/node pair to the statement map.
595 * @param stmt The statement
596 * @param node The node. Must not be null.
597 */
598 private void addStatement(Statement stmt, OASTNode node)
599 {
600 if (stmt == null)
601 {
602 return;
603 }
604
605 List l = (List)_mapStatementToNode.get(stmt);
606 if (l == null)
607 {
608 l = new ArrayList(1);
609 _mapStatementToNode.put(stmt, l);
610 }
611 if (!l.contains(node))
612 {
613 l.add(node);
614 }
615 }
616
617 /***
618 * Returns a Jena model representing the OAST. If the model does not already
619 * exist, it will be built automatically.
620 * @return The Jena model for this OAST
621 */
622 public OntModel getJenaModel()
623 {
624 if (_model == null)
625 {
626 createModel();
627 }
628 return _model;
629 }
630
631 /***
632 * Finds the lowest-level node in the tree that contains a given offset.
633 * @param offset The offset to find
634 * @return The node containing the offset or the document root if no
635 * containing node is found
636 */
637 public OASTNode getNode(int offset)
638 {
639 OASTNode ret = _root.getContainingChild(offset);
640 return (ret == null ? _root : ret);
641 }
642
643 /***
644 * Finds a node that represents a specific Jena statement.
645 * @param stmt The statement to find
646 * @return The node that corresponds to the statement, or null if no such
647 * node is found.
648 */
649 public OASTNode getNode(Statement stmt)
650 {
651 if (_model == null)
652 {
653 createModel();
654 }
655
656 List l = (List)_mapStatementToNode.get(stmt);
657 if (l == null || l.size() == 0)
658 {
659 return null;
660 }
661 return (OASTNode)l.get(0);
662 }
663
664 /***
665 * Finds a node that represents a specific Jena resource.
666 * @param res The resource to find
667 * @return The node that corresponds to the resource, or null if no such
668 * node is found
669 */
670 public OASTNode getNode(Resource res)
671 {
672 if (_model == null)
673 {
674 this.createModel();
675 }
676
677 List l = (List)_mapResourceToNode.get(res);
678 if (l == null || l.size() == 0)
679 {
680 return null;
681 }
682
683 return (OASTNode)l.get(0);
684 }
685
686 /***
687 * Finds a node that represents a specific URI.
688 * @param sUri The URI to find
689 * @return The node that corresponds to the URI, or null if no such node is
690 * found
691 */
692 public OASTNode getNode(String sUri)
693 {
694 if (_model == null)
695 {
696 this.createModel();
697 }
698 Resource res = _model.getResource(sUri);
699 return (getNode(res));
700 }
701
702 /***
703 * <p>Performs a partial parse on a string of text and inserts the resulting
704 * nodes into the tree at a specified offset. The new node(s) will be
705 * parented to the node currently in the OAST at the insertion point and
706 * nodes beyond that point will be displaced to accomodate the new items.</p>
707 *
708 * <p>This method is not intended for public use.</p>
709 *
710 * @param iOffset The insert position
711 * @param sText Text to parse
712 */
713 public void insert(int iOffset, String sText)
714 {
715 OASTNode nodeParent = getNode(iOffset);
716 if (nodeParent != null && nodeParent.getOffset() == iOffset)
717 {
718 nodeParent = nodeParent.getParent();
719 }
720 if (nodeParent == null)
721 {
722 nodeParent = _root;
723 }
724
725 try
726 {
727 String sProlog = getProlog();
728 OASTNode dummy = partialParse(nodeParent, sProlog, sText, iOffset);
729 createEvent(new OASTNode[0], dummy, nodeParent, iOffset, 0);
730 }
731 catch (IOException e)
732 {
733 OWLCore.logError(OWLCore.getID(), "Partial reparse failed", e);
734 }
735 }
736
737 /***
738 * A node visitor that will truncate a node and its children so that
739 * they exist between a given start and end offset.
740 * @author jlerner
741 */
742 private class NodeTruncationVisitor implements IOASTNodeVisitor
743 {
744 private int _iStart, _iEnd;
745 /***
746 * Creates a visitor to truncate nodes starting before or ending after
747 * specific offsets.
748 * @param iStartOffset The desired start offset.
749 * @param iEndOffset The desired end offset.
750 */
751 public NodeTruncationVisitor(int iStartOffset, int iEndOffset)
752 {
753 _iStart = iStartOffset;
754 _iEnd = iEndOffset;
755 }
756
757
758
759
760 public boolean visit(OASTNode node)
761 {
762 try
763 {
764 if (_iEnd <= _iStart)
765 {
766 node.setLength(0);
767 node.setOffset(_iStart);
768 if (node instanceof TextNode)
769 {
770 TextNode tn = (TextNode)node;
771 tn.setText("");
772 }
773 return true;
774 }
775 if (node.getOffset() < _iStart)
776 {
777 int iDiff = _iStart - node.getOffset();
778 node.setLength(iDiff);
779 node.setOffset(_iStart);
780 if (node instanceof TextNode)
781 {
782 TextNode tn = (TextNode)node;
783 if (tn.getLength() <= 0)
784 {
785
786 tn.setText("");
787
788
789 return true;
790 }
791 tn.setText(tn.getText().substring(iDiff));
792 }
793 }
794 int iNodeEnd = node.getOffset() + node.getLength();
795 if (iNodeEnd > _iEnd)
796 {
797 node.setLength(node.getLength() - (iNodeEnd - _iEnd));
798 if (node instanceof TextNode)
799 {
800 TextNode tn = (TextNode)node;
801 if (tn.getLength() <= 0)
802 {
803
804 tn.setText("");
805
806
807 return true;
808 }
809 tn.setText(tn.getText().substring(0, tn.getLength()));
810 }
811 }
812 return true;
813 }
814 catch (OASTException e)
815 {
816
817
818
819 return true;
820 }
821 }
822 }
823
824 /***
825 * <p>Replaces a node in the syntax tree with a new one constructed by
826 * performing a partial reparse on some new text. The nodes created
827 * by the reparse will be positioned in the tree based on the offset
828 * and parent of the node being removed.</p>
829 *
830 * <p>Unparseable regions within the node being replaced are blanked out to
831 * avoid cascading XML errors up the document. The corresponding
832 * UnparseableNodes are reattached to the tree after the new text is parsed
833 * into the OAST. Preserving individual unparseable regions of text allows
834 * XML errors to cascade back down the document if an enclosing node becomes
835 * unparseable and is then fixed.</p>
836 *
837 * <p>This method is not intended for public use.</p>
838 *
839 * @param nodeReplace The node to replace
840 * @param sText Text to parse into replacement nodes
841 * @param iOffset The offset, relative to the document, of the changed text
842 * that caused the node replacement
843 * @param iNewLength net character change in document. Will be negative if
844 * more characters were removed than inserted.
845 */
846 public void replace(OASTNode nodeReplace, String sText, int iOffset,
847 int iNewLength)
848 {
849 StringBuffer sbText = new StringBuffer(sText);
850 ArrayList alUnparseable = new ArrayList(
851 Arrays.asList(nodeReplace.getNodesOfType(OASTNode.UNPARSEABLE)));
852 if (nodeReplace instanceof UnparseableNode)
853 {
854 alUnparseable.remove(nodeReplace);
855 }
856 Iterator it = alUnparseable.iterator();
857 while (it.hasNext())
858 {
859 UnparseableNode node = (UnparseableNode)it.next();
860
861 OASTNode nodeParent = node.getParent();
862 if (nodeParent != null && nodeParent != nodeReplace
863 && nodeParent instanceof UnparseableNode)
864 {
865 continue;
866 }
867
868
869 node.detach(false);
870 int iStart = node.getOffset();
871 int iEnd = iStart + node.getLength();
872 if (iNewLength < 0 && iStart >= iOffset
873 && (iStart + iNewLength) < iOffset)
874 {
875
876
877 iStart = iOffset;
878 iEnd += iNewLength;
879 node.accept(new NodeDisplacementVisitor(iOffset, iNewLength));
880 node.accept(new NodeTruncationVisitor(iStart, iEnd));
881 }
882 else if (iNewLength < 0 && iStart < iOffset
883 && (iEnd > iOffset && iEnd <= (iOffset - iNewLength)))
884 {
885
886
887 iEnd = iOffset;
888 node.accept(new NodeTruncationVisitor(iStart, iEnd));
889 }
890 else if (iStart >= iOffset)
891 {
892
893
894 iStart += iNewLength;
895 iEnd += iNewLength;
896 node.accept(new NodeDisplacementVisitor(iOffset, iNewLength));
897 }
898
899
900
901
902
903
904
905
906
907 iStart -= nodeReplace.getOffset();
908 iEnd -= nodeReplace.getOffset();
909 for(int i = iStart; i < iEnd; i++)
910 {
911 sbText.setCharAt(i, ' ');
912 }
913 }
914
915 int iOffsetReplace = nodeReplace.getOffset();
916 OASTNode nodeParent = nodeReplace.getParent();
917 if (nodeParent == null)
918 {
919 nodeParent = _root;
920 }
921
922
923 OASTNode dummy;
924 try
925 {
926
927
928
929 String sProlog = getProlog(iOffsetReplace, nodeReplace.getLength());
930 dummy = partialParse(nodeParent, sProlog.toString(), sbText.toString(), iOffsetReplace);
931 Collections.sort(alUnparseable, new NodeLevelComparator());
932
933
934 OASTNode[] nodes;
935 if (nodeReplace instanceof DocumentRoot)
936 {
937 nodes = nodeReplace.getChildren();
938
939
940
941 createEvent(nodes, dummy, _root, 0, _root.getLength(), alUnparseable);
942 return;
943 }
944 else
945 {
946 nodes = new OASTNode[]{nodeReplace};
947 createEvent(nodes, dummy, nodeParent, alUnparseable);
948 }
949
950 }
951 catch (IOException e)
952 {
953 OWLCore.logError(OWLCore.getID(), "Partial reparse failed", e);
954 }
955
956 }
957 /***
958 * Replaces a node in the syntax tree with a new one constructed by
959 * performing a partial reparse on some new text. The nodes created
960 * by the reparse will be positioned in the tree based on the offset
961 * and parent of the node being removed.
962 *
963 * @deprecated As of SWeDE 1.0.2, use
964 * {@link #replace(OASTNode, String, int, int)} to automatically
965 * filter out unparseable subregions of the node being replaced.
966 * @param nodeReplace The node to replace
967 * @param sText Text to parse into replacement nodes
968 */
969 public void replace(OASTNode nodeReplace, String sText)
970 {
971 int iOffset = nodeReplace.getOffset();
972 OASTNode nodeParent = nodeReplace.getParent();
973 if (nodeParent == null)
974 {
975 nodeParent = _root;
976 }
977
978
979 OASTNode dummy;
980 try
981 {
982
983
984
985 String sProlog = getProlog(iOffset, nodeReplace.getLength());
986 dummy = partialParse(nodeParent, sProlog.toString(), sText, iOffset);
987 createEvent(new OASTNode[]{nodeReplace}, dummy, nodeParent, 0, 0);
988
989 }
990 catch (IOException e)
991 {
992 OWLCore.logError(OWLCore.getID(), "Partial reparse failed", e);
993 }
994 }
995
996 /***
997 * Creates an OAST delta representing a given set of node removals and
998 * insertions. The correct type of delta will be created depending on
999 * whether the removed or inserted nodes lists are empty.
1000 * @param nodesRemoved the nodes removed for this event
1001 * @param dummy disposable node whose children are the nodes being inserted
1002 * @param parent the parent node for the nodes being inserted
1003 */
1004 private IOASTDelta createDelta(OASTNode[] nodesRemoved, OASTNode dummy, OASTNode parent)
1005 {
1006 IOASTDelta delta = null;
1007 OASTNode[] children = dummy.getChildren();
1008 if (nodesRemoved.length > 0)
1009 {
1010 if (children.length > 0)
1011 {
1012 delta = OASTDelta.createReparseDelta(nodesRemoved, children);
1013 }
1014 else
1015 {
1016 delta = OASTDelta.createRemoveDelta(nodesRemoved);
1017 }
1018 }
1019 else if (children.length > 0)
1020 {
1021 delta = OASTDelta.createInsertDelta(parent, children);
1022 }
1023 return delta;
1024 }
1025 /***
1026 * <p>Creates an OAST change event with corresponding OAST delta. The correct
1027 * type of delta will be created depending on whether the removed or
1028 * inserted nodes lists are empty. The event is automatically applied to the
1029 * tree and notifications are sent to all OAST change listeners.</p>
1030 *
1031 * <p>This is a convenience method, fully equivalent to:
1032 * <blockquote>createEvent(nodesRemoved,dummy,parent,iOffset,iLength,null)
1033 * </blockquote></p>
1034 * @param nodesRemoved the nodes removed for this event
1035 * @param dummy disposable node whose children are the nodes being inserted
1036 * @param parent the parent node for the nodes being inserted
1037 * @param iOffset document offset of the event
1038 * @param iLength number of characters removed in the event
1039 */
1040 private void createEvent(OASTNode[] nodesRemoved, OASTNode dummy,
1041 OASTNode parent, int iOffset, int iLength)
1042 {
1043 createEvent(nodesRemoved, dummy, parent, iOffset, iLength, null);
1044 }
1045
1046 /***
1047 * Creates an OAST change event with corresponding OAST delta. The correct
1048 * type of delta will be created depending on whether the removed or
1049 * inserted nodes lists are empty. The event is automatically applied to the
1050 * tree and notifications are sent to all OAST change listeners.
1051 * @param nodesRemoved the nodes removed for this event
1052 * @param dummy disposable node whose children are the nodes being inserted
1053 * @param parent the parent node for the nodes being inserted
1054 * @param iOffset document offset of the event
1055 * @param iLength number of characters removed in the event
1056 * @param alUnparseable a list of unparseable nodes that need to be
1057 * reattached to the tree after the event is handled
1058 */
1059 private void createEvent(OASTNode[] nodesRemoved, OASTNode dummy,
1060 OASTNode parent, int iOffset, int iLength, ArrayList alUnparseable)
1061 {
1062 IOASTDelta delta = createDelta(nodesRemoved, dummy, parent);
1063 OASTEvent event = (delta == null ? null : new OASTEvent(delta));
1064
1065 if (iOffset >= 0)
1066 {
1067
1068 int iRemove = iLength;
1069 for (int i = 0; i < nodesRemoved.length; i++)
1070 {
1071 iRemove -= nodesRemoved[i].getLength();
1072 }
1073
1074 int iInsert = dummy.getLength();
1075 OASTNode[] children = dummy.getChildren();
1076 for (int i = 0; i < children.length; i++)
1077 {
1078 iInsert -= (children[i]).getLength();
1079 }
1080
1081 applyEvent(event, iOffset, iRemove, iInsert, alUnparseable);
1082 }
1083 else
1084 {
1085 applyEvent(event, iOffset, 0, 0, alUnparseable);
1086 }
1087 }
1088
1089 /***
1090 * Creates an OAST change event with corresponding OAST delta. The correct
1091 * type of delta will be created depending on whether the removed or
1092 * inserted nodes lists are empty. The event is automatically applied to the
1093 * tree and notifications are sent to all OAST change listeners.
1094 * @param nodesRemoved the nodes removed for this event
1095 * @param dummy disposable node whose children are the nodes being inserted
1096 * @param parent the parent node for the nodes being inserted
1097 * @param alUnparseable a list of unparseable nodes that need to be
1098 * reattached to the tree after the event is handled
1099 */
1100 private void createEvent(OASTNode[] nodesRemoved, OASTNode dummy,
1101 OASTNode parent, ArrayList alUnparseable)
1102 {
1103 if (nodesRemoved.length > 0)
1104 {
1105 int iOffset = nodesRemoved[0].getOffset();
1106 int iEnd = nodesRemoved[nodesRemoved.length - 1].getOffset()
1107 + nodesRemoved[nodesRemoved.length - 1].getLength();
1108 int iLength = iEnd - iOffset;
1109 createEvent(nodesRemoved, dummy, parent, iOffset, iLength, alUnparseable);
1110 }
1111 else
1112 {
1113 IOASTDelta delta = createDelta(nodesRemoved, dummy, parent);
1114 OASTEvent event = (delta == null ? null : new OASTEvent(delta));
1115
1116 applyEvent(event, alUnparseable);
1117 }
1118 }
1119
1120 /***
1121 * <p>Replaces a range of nodes in the syntax tree with any number of new nodes
1122 * in a specified ArrayList. The inserted nodes will be parented to the
1123 * parent of the first node removed.</p>
1124 *
1125 * <p>This method is not intended for public use.</p>
1126 *
1127 * @param iOffset Begin offset of the removal
1128 * @param iLength Length of the removal
1129 * @param dummy The dummy parent node resulting from a partial reparse
1130 * executed outside the OAST. The node itself will be discarded,
1131 * but its children will be integrated into the tree.
1132 */
1133 public void replace(int iOffset, int iLength, OASTNode dummy)
1134 {
1135 OASTNode[] nodes = getNodes(iOffset, iLength);
1136
1137
1138 OASTNode nodeParent = (nodes.length > 0 ? nodes[0].getParent()
1139 : getNode(iOffset));
1140 if (nodeParent == null)
1141 {
1142 nodeParent = _root;
1143 }
1144
1145 createEvent(nodes, dummy, nodeParent, iOffset, iLength);
1146
1147 }
1148
1149 /***
1150 * <p>Replaces a range of nodes in the syntax tree with one or more new nodes
1151 * constructed by performing a partial reparse on some new text. The nodes
1152 * created by the reparse will be positioned in the tree based on the offset
1153 * of the remove range and the parent of the first node being removed.</p>
1154 *
1155 * <p>This method is not intended for public use.</p>
1156 *
1157 * @param iOffset Begin offset of the removal
1158 * @param iLength Length of removal
1159 * @param sText Text to parse into replacement nodes
1160 */
1161 public void replace(int iOffset, int iLength, String sText)
1162 {
1163 OASTNode[] nodes = getNodes(iOffset, iLength);
1164
1165
1166 OASTNode nodeParent = (nodes.length > 0 ? nodes[0].getParent()
1167 : getNode(iOffset));
1168 if (nodeParent == null)
1169 {
1170 nodeParent = _root;
1171 }
1172
1173 try
1174 {
1175
1176
1177
1178 String sProlog = getProlog(iOffset, iLength);
1179 OASTNode dummy = partialParse(nodeParent, sProlog, sText, iOffset);
1180 IOASTDelta delta = null;
1181 createEvent(nodes, dummy, nodeParent, iOffset, iLength);
1182
1183 }
1184 catch (IOException e)
1185 {
1186 OWLCore.logError(OWLCore.getID(), "Partial reparse failed", e);
1187 }
1188 }
1189
1190 /***
1191 * Captures and returns the full XML prolog for the document. The XML
1192 * prolog consists of the XML version tag, other processing instructions,
1193 * and DOCTYPE.
1194 * @return A string containing the full XML prolog. This may be an empty
1195 * string.
1196 */
1197 private String getProlog()
1198 {
1199 StringBuffer sbProlog = new StringBuffer();
1200 OASTNode[] children = _root.getChildren();
1201 for (int i = 0; i < children.length; i++)
1202 {
1203 OASTNode n = children[i];
1204 if (!(n instanceof TextNode))
1205 {
1206 break;
1207 }
1208 if (n instanceof ProcessingInstruction
1209 || n instanceof Doctype)
1210 {
1211 String s = ((TextNode)n).getText();
1212 sbProlog.append(s);
1213 }
1214 }
1215 return sbProlog.toString();
1216 }
1217
1218 /***
1219 * Captures and returns the XML prolog (processing instructions & DOCTYPE)
1220 * for the document, excluding any prolog elements that intersect with a
1221 * specified range of the text.
1222 * @param iOffset Begin offset of the ignore region
1223 * @param iLength Length of the ignore region
1224 * @return A string containing the non-intersecting portions of the XML prolog
1225 */
1226 private String getProlog(int iOffset, int iLength)
1227 {
1228 StringBuffer sbProlog = new StringBuffer();
1229 OASTNode[] children = _root.getChildren();
1230 for (int i = 0; i < children.length; i++)
1231 {
1232 OASTNode n = children[i];
1233 if (!(n instanceof TextNode))
1234 {
1235 break;
1236 }
1237 if (n instanceof ProcessingInstruction
1238 || n instanceof Doctype)
1239 {
1240 if (n.getOffset() >= iOffset && n.getOffset() < iOffset + iLength)
1241 {
1242 continue;
1243 }
1244 String s = ((TextNode)n).getText();
1245 sbProlog.append(s);
1246 }
1247 }
1248 return sbProlog.toString();
1249
1250 }
1251
1252 /***
1253 * Removes a node from the OAST. The tree
1254 * will be displaced by an appropriate amount to compensate for the removal.
1255 *
1256 * @deprecated As of SWeDE 2.0.0, use
1257 * {@link OASTNode#remove(OASTNode, boolean)} instead. The OAST
1258 * class's edit operations are now intended exclusively for
1259 * internal use by the editor and related classes.
1260 * @param node The node to remove
1261 * @return The parent of the removed node
1262 */
1263 public OASTNode remove(OASTNode node)
1264 {
1265 OASTNode parent = node.getParent();
1266 try
1267 {
1268 parent.remove(node, true);
1269 }
1270 catch (OASTException e)
1271 {
1272
1273
1274 }
1275
1276 return parent;
1277 }
1278
1279 /***
1280 * <p>Remove a range of nodes from the abstract syntax tree. The tree will be
1281 * displaced to compensate for the removed node(s) as well as surrounding
1282 * whitespace.</p>
1283 *
1284 * <p>This is a convenience method, fully equivalent to:
1285 * <blockquote>replace(iOffset, iLength, "")</blockquote></p>
1286 *
1287 * <p>This method is not intended for public use.</p>
1288 *
1289 * @param iOffset Start offset of the removal. Nodes that start before this
1290 * offset will not be removed.
1291 * @param iLength Length of the removal. Nodes that end after
1292 * iOffset + iLength will not be removed.
1293 */
1294 public void remove(int iOffset, int iLength)
1295 {
1296 replace(iOffset, iLength, "");
1297 }
1298
1299 /***
1300 * Produces an array of nodes contained in a specific text range. The first
1301 * complete node found within the range determines the tree level of the
1302 * search. Only nodes that are both completely contained in the specified
1303 * range and at the same level in the OAST will be returned.
1304 * @param iOffset Start offset of the range. Nodes that start before this
1305 * offset will not be included.
1306 * @param iLength Length of the range. Nodes that end after
1307 * iOffset + iLength will not be included.
1308 * @return An array of nodes contained in the specified range, all at the
1309 * same level in the OAST.
1310 */
1311 protected OASTNode[] getNodes(int iOffset, int iLength)
1312 {
1313 OASTNode parent = getNode(iOffset);
1314 if (parent.getOffset() == iOffset && parent.getLength() < iLength)
1315 {
1316 parent = parent.getParent();
1317 }
1318 OASTNode[] children = parent.getChildren();
1319 ArrayList alReturn = new ArrayList();
1320 if (parent.startsAfter(iOffset) && parent.endsBefore(iOffset + iLength))
1321 {
1322 alReturn.add(parent);
1323 }
1324 else
1325 {
1326 for (int i = 0; i < children.length; i++)
1327 {
1328 OASTNode node = children[i];
1329 if (node.startsAfter(iOffset)
1330 && node.endsBefore(iOffset + iLength))
1331 {
1332 alReturn.add(node);
1333 }
1334 }
1335 }
1336 OASTNode[] nodes = new OASTNode[alReturn.size()];
1337 alReturn.toArray(nodes);
1338 return nodes;
1339 }
1340
1341
1342 /***
1343 * Helper method for <code>applyEvent()</code>. Handles node removals for
1344 * an event, including displacement of the remaining nodes in the tree.
1345 * @param event The event to apply. Must be a non-composite event.
1346 */
1347 private void removeNodes(OASTEvent event)
1348 {
1349 OASTNodeRef[] removed = event.getRemoved(0);
1350 for (int i = 0; i < removed.length; i++)
1351 {
1352 OASTNodeRef nodeRef = removed[i];
1353 nodeRef.getNode().detach(true);
1354 }
1355 }
1356
1357 /***
1358 * Helper method for <code>applyEvent()</code>. Handles node insertions for
1359 * an event, including displacement of the existing nodes in the tree.
1360 * @param event The event to apply. Must be a non-composite event.
1361 */
1362 private void insertNodes(OASTEvent event)
1363 {
1364 OASTNodeRef[] inserted = event.getInserted(0);
1365 for (int i = 0; i < inserted.length; i++)
1366 {
1367 OASTNodeRef nodeRef = inserted[i];
1368 nodeRef.getNode().attach(this, true);
1369 }
1370 }
1371
1372 /***
1373 * <p>Helper method for <code>applyEvent()</code>. Performs additional
1374 * displacement on the tree to compensate for whitespace that was removed
1375 * and/or inserted that is not included in the length of the removed and
1376 * inserted nodes.</p>
1377 *
1378 * <p>Additionally, a list of unparseable nodes that will later be reattached
1379 * to the tree may be specified. If a non-empty list is provided, the
1380 * nodes it contains will also be adjusted to compensate for the extra removed
1381 * and added whitespace.</p>
1382 * @param iOffset The offset of the document event that caused the OAST event
1383 * @param iRemoveWhitespace Number of extra whitespace characters removed
1384 * @param iInsertWhitespace Number of extra whitespace characters inserted
1385 */
1386 private void adjustForWhitespace(int iOffset, int iRemoveWhitespace,
1387 int iInsertWhitespace)
1388 {
1389 if (iRemoveWhitespace > 0)
1390 {
1391 _root.accept(new NodeDisplacementVisitor(iOffset, -iRemoveWhitespace));
1392 }
1393 if (iInsertWhitespace > 0)
1394 {
1395 _root.accept(new NodeDisplacementVisitor(iOffset, iInsertWhitespace));
1396 }
1397 }
1398
1399 /***
1400 * Helper method for <code>applyEvent()</code>. Reattaches a list of
1401 * filtered-out unparseable nodes to the tree after a reparse event has been
1402 * applied.
1403 * @param lUnparseable the list of filtered-out unparseables
1404 */
1405 private void reattachUnparseables(List lUnparseable)
1406 {
1407 if (lUnparseable == null)
1408 {
1409 return;
1410 }
1411 Iterator it = lUnparseable.iterator();
1412 while (it.hasNext())
1413 {
1414 UnparseableNode node = (UnparseableNode)it.next();
1415
1416
1417
1418 if (node.getText().trim().length() == 0)
1419 {
1420 continue;
1421 }
1422 node.attach(this, false);
1423 }
1424 }
1425
1426
1427 /***
1428 * Applies an event caused by text modification to the OAST. This includes
1429 * detaching removed nodes, displacing the tree to compensate for the event,
1430 * and attaching inserted nodes, as well as sending event notifications to
1431 * OAST change listeners. The event will be flagged as a text event before
1432 * OAST change notifications are sent out.
1433 * @param event The OAST event resulting from the text modification. Must
1434 * be a non-composite event.
1435 * @param alUnparseable list of unparseable nodes to manually attach to the
1436 * tree after applying the event
1437 */
1438 private void applyEvent(OASTEvent event, ArrayList alUnparseable)
1439 {
1440 if (event != null)
1441 {
1442 event.setTextEvent(true);
1443 removeNodes(event);
1444 insertNodes(event);
1445 }
1446 reattachUnparseables(alUnparseable);
1447 if (event != null)
1448 {
1449 try
1450 {
1451 changed(event);
1452 }
1453 catch (OASTException e)
1454 {
1455 OWLCore.logError(OWLCore.getID(), "OAST locked during manual text edit.", e);
1456 }
1457 }
1458 }
1459
1460 /***
1461 * Applies an event caused by text modification to the OAST. This includes
1462 * detaching removed nodes, displacing the tree to compensate for the event,
1463 * and attaching inserted nodes, as well as sending event notifications to
1464 * OAST change listeners. The event will be flagged as a text event before
1465 * OAST change notifications are sent out.
1466 * @param event The OAST event resulting from the text modification. Must
1467 * be a non-composite event.
1468 * @param iOffset The offset of the document event that caused the OAST event
1469 * @param iRemoveWhitespace Amount of extra whitespace that must be removed
1470 * at the event offset
1471 * @param iInsertWhitespace Amount of whitespace that must be added at the
1472 * event offset
1473 * @param alUnparseable list of unparseable nodes to manually attach to the
1474 * tree after applying the event
1475 */
1476 private void applyEvent(OASTEvent event, int iOffset, int iRemoveWhitespace,
1477 int iInsertWhitespace, ArrayList alUnparseable)
1478 {
1479 if (event != null)
1480 {
1481 event.setTextEvent(true);
1482 removeNodes(event);
1483 }
1484 adjustForWhitespace(iOffset, iRemoveWhitespace, iInsertWhitespace);
1485 if (event != null)
1486 {
1487 insertNodes(event);
1488 reattachUnparseables(alUnparseable);
1489 try
1490 {
1491 changed(event);
1492 }
1493 catch (OASTException e)
1494 {
1495 OWLCore.logError(OWLCore.getID(), "OAST locked during manual text edit.", e);
1496 }
1497 }
1498 }
1499
1500 /***
1501 * <p>Attempts to parse an XML fragment into a node structure. The resulting
1502 * node(s) are parented to a dummy node which is returned, but are structured
1503 * based on the type of the supplied parent node. It is up to the caller
1504 * to position the dummy node's children correctly for insertion in the tree
1505 * and to create the actual parent/child relationships by calling
1506 * attachChildren</p>
1507 *
1508 * <p>This method is not intended for public use.</p>
1509 *
1510 * @deprecated As of SWeDE 2.0.0, use
1511 * {@link #partialParse(OASTNode, String, String, int)}. This version
1512 * will continue to function as a convenience method for
1513 * <code>partialParse(parent,sProlog,sText,0)</code>.
1514 * @see #attachChildren(OASTNode, OASTNode)
1515 * @param parent The intended parent node
1516 * @param sProlog XML prolog information to prepend to the text when SAX
1517 * parses it
1518 * @param sText The new text to parse
1519 * @return A dummy node whose children represent one or more node structures
1520 * resulting from the parse.
1521 * @throws IOException
1522 */
1523 private OASTNode partialParse(OASTNode parent, String sProlog, String sText)
1524 throws IOException
1525 {
1526 return partialParse(parent, sProlog, sText, 0);
1527 }
1528
1529 /***
1530 * <p>Attempts to parse an XML fragment into a node structure. Any XML prolog
1531 * information alread stored in the OAST is used to ensure proper XML
1532 * encoding and entity substitutions. The resulting node(s) are parented to
1533 * a dummy node which is returned, but are structured based on the type of
1534 * the supplied parent node. The dummy node will be displaced to start at a
1535 * supplied document offset. It is up to the caller to create the actual
1536 * parent/child relationships by calling <code>attachChildren</code>.</p>
1537 *
1538 * <p>This is a convenience method, fully equivalent to:
1539 * <blockquote>partialParse(parent, getProlog(iOffset, sText.length()),
1540 * sText, iOffset)</blockquote></p>
1541 *
1542 * <p>This method is not intended for public use.</p>
1543 *
1544 * @see #attachChildren(OASTNode, OASTNode)
1545 * @see #attachChildren(OASTNode[], OASTNode)
1546 * @param parent Intended parent node for newly parsed nodes
1547 * @param sText Text to parse
1548 * @param iOffset Insertion offset
1549 * @return A dummy node whose children represent one or more node structures
1550 * resulting from the parse.
1551 * @throws IOException if an I/O error occurs during the reparse.
1552 */
1553 public OASTNode partialParse(OASTNode parent, String sText, int iOffset)
1554 throws IOException
1555 {
1556 return partialParse(parent, getProlog(iOffset, sText.length()), sText, iOffset);
1557 }
1558
1559 /***
1560 * A specialized OAST root, usable as a placeholder root node for partial
1561 * parses. DummyRoot differs from DocumentRoot only in its displace
1562 * behavior: DocumentRoot nodes always want to maintain a starting offset
1563 * of 0, so any displacement is handled by lengthening the node. DummyRoots
1564 * may start at any offset and so they displace just like other nodes.
1565 * @author jlerner
1566 */
1567 private class DummyRoot extends DocumentRoot
1568 {
1569 /***
1570 * Creates a dummy root representing a portion of a file.
1571 * @param file The file
1572 */
1573 public DummyRoot(IFile file)
1574 {
1575 super(file, null);
1576 }
1577
1578 public boolean displace(int offset, int length)
1579 {
1580 return OASTNode.displace(this, offset, length);
1581 }
1582 }
1583 /***
1584 * <p>Attempts to parse an XML fragment into a node structure. The resulting
1585 * node(s) are parented to a dummy node which is returned, but are structured
1586 * based on the type of the supplied parent node. The dummy node will be
1587 * displaced to start at a supplied document offset. It is up to the
1588 * caller to create the actual parent/child relationships by calling
1589 * <code>attachChildren</code>.</p>
1590 *
1591 * <p>This method is not intended for public use.</p>
1592 *
1593 * @see #attachChildren(OASTNode, OASTNode)
1594 * @see #attachChildren(OASTNode[], OASTNode)
1595 * @param parent The intended parent node
1596 * @param sProlog XML prolog information to prepend to the text when SAX
1597 * parses it
1598 * @param sText The new text to parse
1599 * @param iOffset The document offset the dummy node should be displaced to
1600 * @return A dummy node whose children represent one or more node structures
1601 * resulting from the parse.
1602 * @throws IOException
1603 */
1604 private OASTNode partialParse(OASTNode parent, String sProlog, String sText, int iOffset)
1605 throws IOException
1606 {
1607 if (sProlog == null)
1608 {
1609 sProlog = "";
1610 }
1611 if (parent == null)
1612 {
1613 parent = _root;
1614 }
1615
1616 OASTNode root;
1617 if (parent instanceof ClassNode)
1618 {
1619 root = new GenericPredicate("");
1620 }
1621 else if (parent instanceof PropertyNode || parent instanceof Rdf)
1622 {
1623 root = new GenericThing("");
1624 }
1625 else
1626 {
1627 IOWLDocument doc = parent.getOWLDocument();
1628 IFile file = (IFile) doc.getCorrespondingResource();
1629
1630
1631 root = new DummyRoot(file);
1632 }
1633 StringBuffer sbText = new StringBuffer(sText);
1634 ArrayList alComments = filterComments(sbText);
1635 OASTStringHandler handler =
1636 new OASTStringHandler(
1637 root,
1638 sbText.toString(),
1639 (parent == _root ? false : true));
1640 if (sbText.toString().trim().length() == 0)
1641 {
1642 if (sText.trim().length() > 0)
1643 {
1644 root.setLength(sText.length());
1645 attachComments(alComments, root);
1646
1647
1648 }
1649 else
1650 {
1651 root.setLength(sText.length());
1652 }
1653 root.accept(new NodeDisplacementVisitor(0, iOffset));
1654 return root;
1655 }
1656 SAXParser parser;
1657 try
1658 {
1659 parser = SAXParserFactory.newInstance().newSAXParser();
1660
1661
1662
1663
1664
1665 StringReader sr;
1666 if (parent == _root)
1667 {
1668 sr = new StringReader(sProlog + sbText.toString());
1669 }
1670 else
1671 {
1672 sr = new StringReader(sProlog + "<a>" + sbText.toString() + "</a>");
1673 }
1674 InputSource is = new InputSource(sr);
1675 try
1676 {
1677 parser.parse(is, handler);
1678 }
1679 catch (Exception e)
1680 {
1681
1682
1683
1684
1685
1686
1687
1688 throw new SAXException(e);
1689 }
1690 OASTNode node;
1691 if (parent != _root)
1692 {
1693 node = (OASTNode) root.getChildren()[0];
1694 }
1695 else
1696 {
1697 node = root;
1698
1699 OASTNode n = (OASTNode)node.getChildren()[0];
1700 XMLPrologParser xpp = new XMLPrologParser();
1701 OASTNode[] nodes = xpp.parse(sbText.toString().substring(0, n.getOffset()));
1702 attachChildren(nodes, node);
1703 }
1704 attachComments(alComments, root);
1705 node.accept(new NodeDisplacementVisitor(0, iOffset));
1706 return node;
1707 }
1708 catch (SAXException se)
1709 {
1710
1711
1712
1713 if (parent == _root)
1714 {
1715 try
1716 {
1717 StringReader sr = new StringReader(sbText.toString() + "<a/>");
1718 SAXParser p = SAXParserFactory.newInstance().newSAXParser();
1719 p.parse(new InputSource(sr), new DefaultHandler());
1720
1721
1722
1723
1724
1725 XMLPrologParser xpp = new XMLPrologParser();
1726 OASTNode[] nodes = xpp.parse(sbText.toString());
1727 OASTNode dummy = new GenericThing("");
1728 dummy.setOffset(0);
1729 dummy.setLength(sbText.toString().length());
1730 attachChildren(nodes, dummy);
1731 attachComments(alComments, dummy);
1732 dummy.accept(new NodeDisplacementVisitor(0, iOffset));
1733 return dummy;
1734 }
1735 catch (SAXException e1)
1736 {
1737 }
1738 catch (ParserConfigurationException e1)
1739 {
1740 }
1741 catch (FactoryConfigurationError e1)
1742 {
1743 }
1744 catch (IOException e1)
1745 {
1746 }
1747 }
1748 OASTNode dummy = new GenericThing("");
1749 UnparseableNode un = new UnparseableNode(sText);
1750 dummy.appendChild(un);
1751 un.setLength(sText.length());
1752 dummy.setOffset(un.getOffset());
1753 dummy.setLength(un.getLength());
1754 dummy.accept(new NodeDisplacementVisitor(0, iOffset));
1755 return dummy;
1756 }
1757 catch (ParserConfigurationException e)
1758 {
1759 OWLCore.logError(
1760 OWLCore.getID(),
1761 "SAX creation failed for partial reparse",
1762 e);
1763 return null;
1764 }
1765 catch (FactoryConfigurationError e)
1766 {
1767 OWLCore.logError(
1768 OWLCore.getID(),
1769 "SAX creation failed for partial reparse",
1770 new Exception(e));
1771 return null;
1772 }
1773 }
1774
1775
1776 /***
1777 * Attaches an array of nodes as children of a specified parent. This
1778 * method doesn't do any node displacement. Offsets and lengths should be
1779 * set before calling attachChildren
1780 * @param nodes Array of child nodes to attach
1781 * @param parent Parent to receive the new child nodes
1782 */
1783 private void attachChildren(OASTNode[] nodes, OASTNode parent)
1784 {
1785 for (int i = 0; i < nodes.length; i++)
1786 {
1787 OASTNode n = nodes[i];
1788 if (parent.getOWLAbstractSyntaxTree() == this)
1789 {
1790
1791
1792 n.attach(this, false);
1793 }
1794 else
1795 {
1796 try
1797 {
1798 parent.insert(n, false);
1799 }
1800 catch (OASTException e)
1801 {
1802
1803
1804 OWLCore.logWarning(OWLCore.getID(), "Unable to attach children during partial parse", e);
1805 }
1806 }
1807 }
1808
1809 }
1810
1811 /***
1812 * Attaches the children of a given node to a specified parent. This method
1813 * doesn't do any node displacement. Offsets and lengths should be set
1814 * before calling attachChildren.
1815 * @param node The node whose children will be attached
1816 * @param parent Node to act as the parent for the attached children
1817 */
1818 private void attachChildren(OASTNode node, OASTNode parent)
1819 {
1820 OASTNode[] children = node.getChildren();
1821 attachChildren(children, parent);
1822 }
1823
1824 /***
1825 * Finds the IOWLDocument corresponding to a namespace URI.
1826 * @param uri URI of the namespace to locate
1827 * @return The IOWLDocument for the specified URI, or null if the project
1828 * does not contain a document with the specified base URI
1829 */
1830 public IOWLDocument getNamespace(String uri)
1831 {
1832 if (uri == null || uri.length() == 0)
1833 {
1834
1835 return (IOWLDocument) SWResourceManager.getModel().getCorrespondingElement(
1836 _file);
1837 }
1838 IOWLProject proj =
1839 (IOWLProject) SWResourceManager.getModel().getCorrespondingElement(
1840 _file.getProject());
1841 if (uri.endsWith("#"))
1842 {
1843 uri = uri.substring(0, uri.length() - 1);
1844 }
1845 return proj.getDocument(uri);
1846 }
1847
1848 /***
1849 * Returns the base URI for the document represented by this OAST.
1850 * @return The document's base URI
1851 */
1852 public String getBaseURI()
1853 {
1854 IOWLDocument doc = getNamespace(null);
1855 if (doc != null)
1856 {
1857 return doc.getURI();
1858 }
1859 return "";
1860 }
1861
1862 /***
1863 * Lists all namespace URIs in the current project.
1864 * @return A java.util.List of all known document URIs
1865 */
1866 public List listNamespaces()
1867 {
1868 IOWLProject proj =
1869 (IOWLProject) SWResourceManager.getModel().getCorrespondingElement(
1870 _file.getProject());
1871 final List l = new ArrayList();
1872 IOWLElementVisitor ev = new IOWLElementVisitor()
1873 {
1874 public boolean visit(IOWLElement element)
1875 {
1876 if (element instanceof IOWLDocument)
1877 {
1878 IOWLDocument doc = (IOWLDocument) element;
1879 if (!l.contains(doc.getURI()))
1880 {
1881 l.add(doc.getURI());
1882 }
1883 }
1884 return true;
1885 }
1886 };
1887 proj.accept(ev);
1888 return l;
1889 }
1890
1891 /***
1892 * Saves a snapshot of which the document's unparseable regions to the
1893 * project metadata.
1894 */
1895 public synchronized void persistUnparseability()
1896 {
1897 OASTNode[] nodes = _root.getNodesOfType(OASTNode.UNPARSEABLE);
1898 if (nodes.length == 0)
1899 {
1900 try
1901 {
1902 _file.setPersistentProperty(QNAME_UNPARSEABLE, null);
1903 }
1904 catch (CoreException e)
1905 {
1906 OWLCore.logWarning(
1907 OWLCore.getID(),
1908 "Unable to set persistent property",
1909 e);
1910 }
1911 return;
1912 }
1913 StringBuffer sbUnparseable = new StringBuffer();
1914 for (int i = 0; i < nodes.length; i++)
1915 {
1916 OASTNode node = nodes[i];
1917 sbUnparseable.append(node.getOffset() + " " + node.getLength() + " ");
1918 }
1919 try
1920 {
1921 _file.setPersistentProperty(
1922 QNAME_UNPARSEABLE,
1923 sbUnparseable.toString());
1924 }
1925 catch (CoreException e)
1926 {
1927 OWLCore.logWarning(
1928 OWLCore.getID(),
1929 "Unable to set persistent property",
1930 e);
1931 }
1932 }
1933
1934 /***
1935 * <p>Reparses the entire document from the file and the unparseability state
1936 * state stored in the metadata. Use this method to revert changes that
1937 * have been made to the OAST as a result of unsaved changes in the editor.</p>
1938 *
1939 * <p>This method is not intended for public use.</p>
1940 *
1941 */
1942 public void reload()
1943 {
1944 try
1945 {
1946 _file.deleteMarkers(S_UNPARSEABLE, false, IResource.DEPTH_INFINITE);
1947 }
1948 catch (CoreException e)
1949 {
1950 OWLCore.logWarning(
1951 OWLCore.getID(),
1952 "Unable to delete markers for full reparse",
1953 e);
1954 }
1955 OASTNode[] oldNodes = _root.getChildren();
1956 OASTNode dummy = doParseDocument(_file);
1957 restorePersistedUnparseables(_file, dummy);
1958 createEvent(oldNodes, dummy, _root, 0, _root.getLength());
1959 }
1960
1961 /***
1962 * Registers a new OASTChangeListener. Listeners receive notifications
1963 * after nodes are added or removed from the OAST.
1964 * @param ocl The listener to register
1965 */
1966 public void addOASTChangeListener(IOASTChangeListener ocl)
1967 {
1968 _alListeners.add(ocl);
1969 }
1970
1971 /***
1972 * Unregisters an OASTChangeListener. It will no longer receive
1973 * OASTChange notifications.
1974 * @param ocl The listener to unregister. If ocl is not registered as an
1975 * OASTChangeListener, this method does nothing.
1976 */
1977 public void removeOASTChangeListener(IOASTChangeListener ocl)
1978 {
1979 _alListeners.remove(ocl);
1980 }
1981
1982 /***
1983 * <p>Registers an OASTChangeListener that gets notified of modifications before
1984 * those registered with addOASTChangeListener.</p>
1985 *
1986 * <p>This method is not intended for public use.</p>
1987 * @param ocl The listener to register
1988 */
1989 public void addPrenotifiedOASTChangeListener(IOASTChangeListener ocl)
1990 {
1991 _alPrenotifiedListeners.add(ocl);
1992 }
1993
1994 /***
1995 * <p>Unregisters a prenotified OASTChangeListener. It will no longer receive
1996 * OASTChange notifications.</p>
1997 *
1998 * <p>This method is not intended for public use.</p>
1999 * @param ocl The listener to unregister. If ocl is not registered as a
2000 * prenotified OASTChangeListener, this method does nothing.
2001 */
2002 public void removePrenotifiedOASTChangelistener(IOASTChangeListener ocl)
2003 {
2004 _alPrenotifiedListeners.remove(ocl);
2005 }
2006
2007 private void preDispatchEvent(OASTEvent event)
2008 {
2009 _bPreLock = true;
2010 _preEvent = new OASTEvent(event);
2011 Iterator it = _alPrenotifiedListeners.iterator();
2012 try
2013 {
2014 while (it.hasNext())
2015 {
2016 IOASTChangeListener ocl = (IOASTChangeListener)it.next();
2017 ocl.oastChanged(event);
2018 }
2019 }
2020 finally
2021 {
2022 _bPreLock = false;
2023 }
2024 }
2025 private void dispatchEvent(OASTEvent event)
2026 {
2027 _bLock = true;
2028 try
2029 {
2030 for (int i = 0; i < _alListeners.size(); i++)
2031 {
2032 IOASTChangeListener ocl = (IOASTChangeListener)_alListeners.get(i);
2033 ocl.oastChanged(event);
2034 }
2035 }
2036 finally
2037 {
2038 _bLock = false;
2039 }
2040 }
2041
2042 private boolean _bLock;
2043 private boolean _bPreLock;
2044 private OASTEvent _preEvent;
2045 /***
2046 * Notifies all OASTChangeListeners that the OAST has just changed.
2047 * @param event Describes the change that was just made
2048 * @throws OASTException if the OAST is locked for modifications.
2049 */
2050 protected synchronized void changed(OASTEvent event) throws OASTException
2051 {
2052 if (_bLock)
2053 {
2054 throw new OASTException("The OAST is locked for modifications.", this);
2055 }
2056 if (_iCollectEvents > 0)
2057 {
2058 if (_compositeEvent == null)
2059 {
2060 _compositeEvent = event;
2061 }
2062 else
2063 {
2064 _compositeEvent.combine(event);
2065 }
2066 return;
2067 }
2068
2069
2070
2071
2072
2073
2074 if (_bPreLock)
2075 {
2076 _preEvent.combine(event);
2077 return;
2078 }
2079
2080
2081 if (!event.isTextEvent())
2082 {
2083 IOWLDocument doc = getRoot().getOWLDocument();
2084 try
2085 {
2086 OWLCore.openEditor(doc);
2087 }
2088 catch (PartInitException e)
2089 {
2090 OWLCore.logError(OWLCore.getID(), "Error opening editor for " + doc.getElementName(), e);
2091 }
2092 }
2093
2094 preDispatchEvent(event);
2095 dispatchEvent(_preEvent);
2096 _preEvent = null;
2097 }
2098
2099 private int _iCollectEvents;
2100 private OASTEvent _compositeEvent;
2101
2102
2103
2104 public synchronized void run(IOASTRunnable action) throws OASTException
2105 {
2106 _iCollectEvents++;
2107 try
2108 {
2109 action.run();
2110 }
2111 finally
2112 {
2113 _iCollectEvents--;
2114 if (_iCollectEvents <= 0 && _compositeEvent != null)
2115 {
2116 OASTNode nodePrimary = action.getPrimaryNode();
2117 _compositeEvent.setPrimaryNode(nodePrimary);
2118 _iCollectEvents = 0;
2119 changed(_compositeEvent);
2120 _compositeEvent = null;
2121 }
2122 }
2123 }
2124
2125
2126
2127
2128
2129 public boolean hasModel()
2130 {
2131 return (_model != null);
2132 }
2133 }