1 19 package org.netbeans.modules.xml.xdm; 20 21 import java.beans.PropertyChangeListener ; 22 import java.beans.PropertyChangeSupport ; 23 import java.io.IOException ; 24 import java.util.ArrayList ; 25 import java.util.Collection ; 26 import java.util.List ; 27 import java.util.Map ; 28 import javax.swing.event.UndoableEditEvent ; 29 import javax.swing.event.UndoableEditListener ; 30 import javax.swing.text.BadLocationException ; 31 import javax.swing.undo.CompoundEdit ; 32 import javax.swing.undo.UndoableEdit ; 33 import javax.swing.undo.UndoableEditSupport ; 34 import javax.xml.XMLConstants ; 35 import javax.xml.namespace.QName ; 36 import org.netbeans.editor.BaseDocument; 37 import org.netbeans.modules.xml.xam.ModelSource; 38 import org.netbeans.modules.xml.xam.dom.ElementIdentity; 39 import org.netbeans.modules.xml.xdm.diff.DiffFinder; 40 import org.netbeans.modules.xml.xdm.diff.XDMTreeDiff; 41 import org.netbeans.modules.xml.xdm.diff.Difference; 42 import org.netbeans.modules.xml.xdm.diff.Add; 43 import org.netbeans.modules.xml.xdm.diff.Change; 44 import org.netbeans.modules.xml.xdm.diff.Delete; 45 import org.netbeans.modules.xml.xdm.diff.DefaultElementIdentity; 46 import org.netbeans.modules.xml.xdm.diff.MergeDiff; 47 import org.netbeans.modules.xml.xdm.diff.NodeIdDiffFinder; 48 import org.netbeans.modules.xml.xdm.diff.NodeInfo; 49 import org.netbeans.modules.xml.xdm.diff.SyncPreparation; 50 import org.netbeans.modules.xml.xdm.nodes.Attribute; 51 import org.netbeans.modules.xml.xdm.nodes.Document; 52 import org.netbeans.modules.xml.xdm.nodes.Element; 53 import org.netbeans.modules.xml.xdm.nodes.Node; 54 import org.netbeans.modules.xml.xdm.nodes.NodeImpl; 55 import org.netbeans.modules.xml.xdm.nodes.Text; 56 import org.netbeans.modules.xml.xdm.nodes.Token; 57 import org.netbeans.modules.xml.xdm.nodes.XMLSyntaxParser; 58 import org.netbeans.modules.xml.xdm.visitor.FindVisitor; 59 import org.netbeans.modules.xml.xdm.visitor.FlushVisitor; 60 import org.netbeans.modules.xml.xdm.visitor.NamespaceRefactorVisitor; 61 import org.netbeans.modules.xml.xdm.visitor.PathFromRootVisitor; 62 import org.netbeans.modules.xml.xdm.visitor.Utils; 63 import org.w3c.dom.NamedNodeMap ; 64 import org.w3c.dom.NodeList ; 65 66 68 public class XDMModel { 69 70 74 public XDMModel(ModelSource ms) { 75 source = ms; 76 assert getSwingDocument() != null; 77 ues = new UndoableEditSupport (this); 78 pcs = new PropertyChangeSupport (this); 79 parser = new XMLSyntaxParser(); 80 setStatus(Status.UNPARSED); 81 82 ElementIdentity eID = createElementIdentity(); 85 setElementIdentity(eID); 86 } 87 88 public String getIndentation() { 89 return currentIndent; 90 } 91 92 public void setIndentation(String indent) { 93 currentIndent = indent; 94 indentInitialized = true; 95 } 96 97 private void setDefaultIndentation() { 98 currentIndent = DEFAULT_INDENT; 99 } 100 101 106 private ElementIdentity createElementIdentity() { 107 ElementIdentity eID = new DefaultElementIdentity(); 109 eID.addIdentifier( "id" ); 112 eID.addIdentifier( "name" ); 113 eID.addIdentifier( "ref" ); 114 return eID; 115 } 116 117 120 public synchronized void flush() { 121 flushDocument(getDocument()); 122 } 123 124 130 public synchronized void sync() throws IOException { 131 if (preparation == null) { 132 prepareSync(); 133 } 134 finishSync(); 135 } 136 137 public synchronized void prepareSync() { 138 Status oldStat = getStatus(); 139 try { 140 setStatus(Status.PARSING); Document newDoc = parser.parse(getSwingDocument()); 142 Document oldDoc = getCurrentDocument(); 143 if (oldDoc == null) { 144 preparation = new SyncPreparation(newDoc); 145 } else { 146 newDoc.assignNodeIdRecursively(); 147 XDMTreeDiff treeDiff = new XDMTreeDiff(eID); 148 List <Difference> preparedDiffs = treeDiff.performDiff( this, newDoc ); 149 preparation = new SyncPreparation(oldDoc, preparedDiffs); 150 } 151 } catch (BadLocationException ble) { 152 preparation = new SyncPreparation(ble); 153 } catch (IllegalArgumentException iae) { 154 preparation = new SyncPreparation(iae); 155 } catch (IOException ioe) { 156 preparation = new SyncPreparation(ioe); 157 } finally { 158 setStatus(oldStat); } 160 } 161 162 private SyncPreparation preparation = null; 163 164 private void finishSync() throws IOException { 165 if (preparation == null) { 166 return; } 168 169 if (preparation.hasErrors()) { 170 IOException error = preparation.getError(); 171 preparation = null; 172 setStatus(Status.BROKEN); 173 throw error; 174 } 175 176 Status savedStatus = getStatus(); 177 setStatus(Status.PARSING); 178 Document oldDoc = getCurrentDocument(); 179 try { 180 if (preparation.getNewDocument() != null) { 181 Document newDoc = preparation.getNewDocument(); 182 newDoc.addedToTree(this); 183 setDocument(newDoc); 184 } else { 185 assert preparation.getOldDocument() != null : "Invalid preparation oldDoc is null"; 186 if (oldDoc != preparation.getOldDocument()) { 187 setStatus(savedStatus); 189 return; 190 } 191 List <Difference> diffs = preparation.getDifferences(); 192 mergeDiff(diffs); 193 fireDiffEvents(diffs); 195 if (getCurrentDocument() != oldDoc) { 196 fireUndoableEditEvent(getCurrentDocument(), oldDoc); 197 } 198 } 199 setStatus(Status.STABLE); 200 } catch (IllegalArgumentException iae) { 201 if (getStatus() != Status.STABLE) { 202 IOException ioe = new IOException (); 203 ioe.initCause(iae); 204 throw ioe; 205 } else { 206 if (iae.getCause() instanceof IOException ) { 208 setStatus(Status.BROKEN); 209 throw (IOException ) iae.getCause(); 210 } else { 211 throw iae; 212 } 213 } 214 } finally { 215 if(getStatus() != Status.STABLE) { 216 setStatus(Status.BROKEN); 217 setDocument(oldDoc); 218 } 219 preparation = null; 220 } 221 } 222 223 public void mergeDiff(List <Difference> diffs) { 224 setStatus(Status.PARSING); 225 new MergeDiff().merge(this, diffs); 226 setStatus(Status.STABLE); 228 } 229 230 private void fireDiffEvents(final List <Difference> deList) { 231 for ( Difference de:deList ) { 232 NodeInfo.NodeType nodeType = de.getNodeType(); 233 if ( de instanceof Add ) { 235 NodeInfo newNodeInfo = ((Add)de).getNewNodeInfo(); 236 assert newNodeInfo != null; 237 pcs.firePropertyChange( PROP_ADDED, null, newNodeInfo ); 238 } else if ( de instanceof Delete ) { 239 NodeInfo OldNodeInfo = ((Delete)de).getOldNodeInfo(); 240 assert OldNodeInfo != null; 241 pcs.firePropertyChange( PROP_DELETED, OldNodeInfo, null ); 242 } else if ( de instanceof Change ) { 243 NodeInfo oldNodeInfo = ((Change)de).getOldNodeInfo(); 244 assert oldNodeInfo != null; 245 246 NodeInfo newNodeInfo = ((Change)de).getNewNodeInfo(); 247 assert newNodeInfo != null; 248 249 if ( ((Change)de).isPositionChanged() ) { 251 pcs.firePropertyChange( PROP_DELETED, oldNodeInfo, null ); 252 pcs.firePropertyChange( PROP_ADDED, null, newNodeInfo ); 253 } else if ( de.getNodeType() == NodeInfo.NodeType.TEXT ) { pcs.firePropertyChange( PROP_MODIFIED, oldNodeInfo, newNodeInfo ); 255 } else if ( de.getNodeType() == NodeInfo.NodeType.ELEMENT ) { 256 List <Node> path1 = new ArrayList <Node>(oldNodeInfo.getOriginalAncestors()); 257 path1.add(0, oldNodeInfo.getNode()); 258 List <Node> path2 = new ArrayList <Node>(newNodeInfo.getNewAncestors()); 259 path2.add(0, newNodeInfo.getNode()); 260 List <Change.AttributeDiff> attrChanges = ((Change)de).getAttrChanges(); 262 for (Change.AttributeDiff attrDiff:attrChanges) { 263 Node oldAttr = attrDiff.getOldAttribute(); 264 Node newAttr = attrDiff.getNewAttribute(); 265 if(attrDiff instanceof Change.AttributeAdd) { 266 assert newAttr != null; 267 pcs.firePropertyChange( PROP_ADDED, null, 268 new NodeInfo(newAttr, -1, path1, path2)); 269 } else if(attrDiff instanceof Change.AttributeDelete) { 270 assert oldAttr != null; 271 pcs.firePropertyChange( 272 PROP_DELETED, new NodeInfo(oldAttr, -1, path1, path2), null ); 273 } else if(attrDiff instanceof Change.AttributeChange) { 274 assert oldAttr != null; 275 assert newAttr != null; 276 NodeInfo old = new NodeInfo(oldAttr, -1, path1, path2); 277 NodeInfo now = new NodeInfo(newAttr, -1, path1, path2); 278 pcs.firePropertyChange( PROP_MODIFIED, old, now); 279 } 280 } 281 } 282 } 283 } 284 } 285 286 private interface Updater { 287 void update(Node parent, Node oldNode, Node newNode); 288 } 289 290 private List <Node> getPathToRoot(Node node, Document root) { 291 PathFromRootVisitor pathVisitor = new PathFromRootVisitor(); 292 List <Node> path = pathVisitor.findPath(root, node); 293 if (path==null || path.isEmpty()) { 294 throw new IllegalArgumentException ("old node must be in the tree"); 295 } 296 return path; 297 } 298 299 private static String classifyMutationType(Node oldNode, Node newNode) { 300 if (newNode == null && oldNode == null || 301 newNode != null && oldNode != null) { 302 return PROP_MODIFIED; 303 } else if (newNode != null) { 304 return PROP_ADDED; 305 } else { 306 return PROP_DELETED; 307 } 308 } 309 310 private enum MutationType { CHILDREN, ATTRIBUTE, BOTH } 311 312 private List <Node> mutate(Node parent, Node oldNode, Node newNode, Updater updater) { 313 return mutate(parent, oldNode, newNode, updater, null); 314 } 315 316 private List <Node> mutate(Node parent, Node oldNode, Node newNode, Updater updater, MutationType type) { 317 checkStableOrParsingState(); 318 if (newNode != null) checkNodeInTree(newNode); 319 320 Document currentDocument = getDocument(); 321 List <Node> ancestors; 322 if (parent == null) { 323 assert(oldNode != null); 324 ancestors = getPathToRoot(oldNode, currentDocument); 325 oldNode = ancestors.remove(0); 326 } else { 327 if (oldNode != null) { 328 assert parent.getIndexOfChild(oldNode) > -1; 329 ancestors = getPathToRoot(oldNode, currentDocument); 330 assert oldNode.getId() == ancestors.get(0).getId(); 331 oldNode = ancestors.remove(0); 332 assert parent.getId() == ancestors.get(0).getId(); 333 } else { 334 ancestors = getPathToRoot(parent, currentDocument); 335 } 336 } 337 338 final Node oldParent = ancestors.remove(0); 339 Node newParent; 340 if (type == null) { 341 if (oldNode instanceof Attribute || newNode instanceof Attribute || 342 (oldNode == null && newNode == null)) { 343 type = MutationType.ATTRIBUTE; 344 } else if (ancestors.size() == 1) { 345 assert (oldParent instanceof Element); 346 type = MutationType.BOTH; 348 } else { 349 type = MutationType.CHILDREN; 350 } 351 } 352 switch(type) { 353 case ATTRIBUTE: 354 newParent = (Node)oldParent.clone(true,true,false); 355 break; 356 case CHILDREN: 357 newParent = (Node)oldParent.clone(true,false,true); 358 break; 359 default: 360 newParent = (Node)oldParent.clone(true,true,true); 361 } 362 363 if (oldNode != null && oldNode.getNodeType() != Node.TEXT_NODE && newNode == null) { undoPrettyPrint(newParent, oldNode, oldParent); 365 } 366 updater.update(newParent, oldNode, newNode); 367 if (oldNode == null && newNode != null && newNode.getNodeType() != Node.TEXT_NODE ) { doPrettyPrint(newParent, newNode, oldParent); 369 } 370 371 List <Node> newAncestors = updateAncestors(ancestors, newParent, oldParent); 372 if(getStatus() != Status.PARSING && newNode instanceof Element) { 373 consolidateNamespaces(newAncestors, newParent, (Element)newNode); 374 } 375 Document d = (Document) (!newAncestors.isEmpty() ? 376 newAncestors.get(newAncestors.size()-1) : newParent); 377 d.addedToTree(this); 378 setDocument(d); 379 ancestors.add(0, oldParent); 380 newAncestors.add(0, newParent); 381 if(getStatus() != Status.PARSING) { fireUndoableEditEvent(d, currentDocument); 383 String mutationType = classifyMutationType(oldNode, newNode); 384 NodeInfo newNodeInfo = new NodeInfo( newNode, -1, ancestors, newAncestors ); 386 pcs.firePropertyChange(mutationType, null, newNodeInfo); 387 } 388 return newAncestors; 389 } 390 391 private void consolidateNamespaces(List <Node> ancestors, Node parent, Element newNode) { 392 if (parent instanceof Document) return; assert ancestors.size() > 0; 394 Element root = (Element) (ancestors.size() == 1 ? 395 parent : ancestors.get(ancestors.size()-2)); 396 List <Node> parentAndAncestors = new ArrayList (ancestors); 397 parentAndAncestors.add(0, parent); 398 consolidateAttributePrefix(parentAndAncestors, newNode); 399 NamedNodeMap nnm = newNode.getAttributes(); 400 for (int i=0; i<nnm.getLength(); i++) { 401 if (nnm.item(i) instanceof Attribute) { 402 Attribute attr = (Attribute) nnm.item(i); 403 consolidateNamespace(root, parentAndAncestors, newNode, attr); 404 } 405 } 406 407 String parentNS = parent.getNamespaceURI(); 409 String parentPrefix = parent.getPrefix(); 410 if (parentNS != null && ! parentNS.equals(XMLConstants.NULL_NS_URI)) { 411 new NamespaceRefactorVisitor(this).refactor( 412 newNode, parentNS, parentPrefix, parentAndAncestors); 413 } 414 } 415 416 private void consolidateAttributePrefix(List <Node> parentAndAncestors, Element newNode) { 417 NamedNodeMap nnm = newNode.getAttributes(); 418 for (int i=0; i<nnm.getLength(); i++) { 419 if (! (nnm.item(i) instanceof Attribute)) continue; 420 Attribute attr = (Attribute) nnm.item(i); 421 String prefix = attr.getPrefix(); 422 if (prefix != null && ! attr.isXmlnsAttribute()) { 423 String namespace = newNode.lookupNamespaceURI(prefix); 424 if (namespace == null) continue; 425 prefix = NodeImpl.lookupPrefix(namespace, parentAndAncestors); 426 if (prefix != null) { 427 attr.setPrefix(prefix); 428 } 429 } 430 } 431 } 432 433 437 private void consolidateNamespace(Element root, List <Node> parentAndAncestors, 438 Element newNode, Attribute attr) { 439 if (attr.isXmlnsAttribute()) { 440 String prefix = attr.getLocalName(); 441 if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) { 442 prefix = XMLConstants.DEFAULT_NS_PREFIX; 443 } 444 String namespace = attr.getValue(); 445 assert (namespace != null); 446 447 Node parent = parentAndAncestors.get(0); 448 String parentNS = parent.getNamespaceURI(); 449 450 String existingNS = NodeImpl.lookupNamespace(prefix, parentAndAncestors); 451 String existingPrefix = NodeImpl.lookupPrefix(namespace, parentAndAncestors); 452 453 458 if (existingNS == null && existingPrefix == null) { newNode.removeAttributeNode(attr); 460 root.appendAttribute(attr); 461 } else if (namespace.equals(existingNS) && prefix.equals(existingPrefix)) { assert prefix.equals(existingPrefix) : "prefix='"+prefix+"' existingPrefix='"+existingPrefix+"'"; 463 newNode.removeAttributeNode(attr); 464 } else if (existingPrefix != null) { if (! namespace.equals(parentNS)) { 468 new NamespaceRefactorVisitor(this).refactor( 469 newNode, namespace, existingPrefix, parentAndAncestors); 470 } 471 } else { } 474 } 475 } 476 477 485 public synchronized List <Node> modify(Node oldValue, Node newValue) { 486 if (oldValue.getId() != newValue.getId()) { 487 throw new IllegalArgumentException ("newValue must be a clone of oldValue"); 488 } 489 490 if (oldValue instanceof Document) { 491 assert newValue instanceof Document; 492 Document oldDoc = (Document) oldValue; 493 Document newDoc = (Document) newValue; 494 newDoc.addedToTree(this); 495 setDocument(newDoc); 496 if (getStatus() != Status.PARSING) { fireUndoableEditEvent(oldDoc, currentDocument); 498 String mutationType = classifyMutationType(oldDoc, newDoc); 499 ArrayList <Node> ancestors = new ArrayList <Node>(); 500 NodeInfo oldNodeInfo = new NodeInfo( oldDoc, -1, ancestors, ancestors ); 501 NodeInfo newNodeInfo = new NodeInfo( newDoc, -1, ancestors, ancestors ); 502 pcs.firePropertyChange(mutationType, oldNodeInfo, newNodeInfo); 503 } 504 return new ArrayList <Node>(); 505 } 506 507 Updater modifier = new Updater() { 508 public void update(Node newParent, Node oldNode, Node newNode) { 509 if (oldNode instanceof Attribute) { 510 ((Element)newParent).replaceAttribute((Attribute)newNode,(Attribute)oldNode); 511 } else { 512 newParent.replaceChild(newNode, oldNode); 513 } 514 } 515 }; 516 return mutate(null, oldValue, newValue, modifier); 517 } 518 519 530 public synchronized List <Node> add(Node parent, Node node, final int offset) { 531 if (offset<0) throw new IndexOutOfBoundsException (); 532 Updater adder = new Updater() { 533 public void update(Node newParent, Node oldNode, Node newNode) { 534 if (newParent instanceof Element && newNode instanceof Attribute) { 535 Element newElement = (Element)newParent; 536 if (offset>newElement.getAttributes().getLength()) 537 throw new IndexOutOfBoundsException (); 538 newElement.addAttribute((Attribute)newNode,offset); 539 } else { 540 if(newParent instanceof Document && newNode instanceof Element) 542 resetIdMeter(); 543 544 if (offset>newParent.getChildNodes().getLength()) 545 throw new IndexOutOfBoundsException (); 546 if(offset<newParent.getChildNodes().getLength()) { 547 Node refChild = (Node)newParent.getChildNodes().item(offset); 548 newParent.insertBefore(newNode,refChild); 549 } else { 550 newParent.appendChild(newNode); 551 } 552 } 553 } 554 }; 555 return mutate(parent, null, node, adder); 556 } 557 558 568 public synchronized List <Node> insertBefore(Node parent, Node node, Node refChild) { 569 final Node ref = refChild; 570 Updater updater = new Updater() { 571 public void update(Node newParent, Node oldNode, Node newNode) { 572 newParent.insertBefore(newNode, ref); 573 } 574 }; 575 576 return mutate(parent, null, node, updater); 577 } 578 579 587 public synchronized List <Node> append(Node parent, Node node) { 588 Updater appender = new Updater() { 589 public void update(Node parent, Node oldNode, Node newNode) { 590 if(parent instanceof Document && newNode instanceof Element) 592 resetIdMeter(); 593 parent.appendChild(newNode); 594 } 595 }; 596 return mutate(parent, null, node, appender); 597 } 598 599 604 public synchronized List <Node> delete(Node n) { 605 Updater remover = new Updater() { 606 public void update(Node newParent, Node oldNode, Node newNode) { 607 newParent.removeChild(oldNode); 608 } 609 }; 610 return mutate(null, n, null, remover); 611 } 612 613 619 public synchronized List <Node> reorder(Node parent, Node n, final int index) { 620 if (index < 0) throw new IndexOutOfBoundsException ("index="+index); 621 Updater u = new Updater() { 622 public void update(Node newParent, Node oldNode, Node newNode) { 623 if (newParent instanceof Element && newNode instanceof Attribute) { 624 Element parent = (Element) newParent; 625 int i = index; 626 if (index > parent.getAttributes().getLength()) { 627 i = parent.getAttributes().getLength(); 628 } 629 parent.reorderAttribute((Attribute) oldNode, i); 630 } else { 631 int i = index; 632 if (index > newParent.getChildNodes().getLength()) { 633 i = newParent.getChildNodes().getLength(); 634 } 635 ((NodeImpl)newParent).reorderChild(oldNode, i); 636 } 637 } 638 }; 639 return mutate(parent, n, null, u); 640 } 641 642 648 public synchronized List <Node> reorderChildren(Node parent, final int[] permutation) { 649 Updater u = new Updater() { 650 public void update(Node newParent, Node oldNode, Node newNode) { 651 ((NodeImpl)newParent).reorderChildren(permutation); 652 } 653 }; 654 return mutate(parent, null, null, u, MutationType.CHILDREN); 655 } 656 657 663 public synchronized List <Node> remove(final Node parent, Node child) { 664 Updater remover = new Updater() { 665 public void update(Node newParent, Node oldNode, Node newNode) { 666 assert parent.isEquivalentNode(newParent); 667 newParent.removeChild(oldNode); 668 } 669 }; 670 return mutate(parent, child, null, remover); 671 } 672 673 679 public synchronized List <Node> removeChildNodes(final Node parent, final Collection <Node> toRemove) { 680 Updater remover = new Updater() { 681 public void update(Node newParent, Node oldNode, Node newNode) { 682 assert parent.isEquivalentNode(newParent); 683 for (Node n : toRemove) { 684 newParent.removeChild(n); 685 } 686 } 687 }; 688 return mutate(parent, null, null, remover, MutationType.CHILDREN); 689 } 690 691 public synchronized List <Node> replaceChild(final Node parent, Node child, Node newChild) { 692 Updater updater = new Updater() { 693 public void update(Node newParent, Node oldNode, Node newNode) { 694 assert newParent.isEquivalentNode(parent); 695 newParent.replaceChild(newNode, oldNode); 696 } 697 }; 698 return mutate(null, child, newChild, updater); 699 } 700 701 711 public synchronized List <Node> setAttribute(Element element, final String name, final String value) { 712 Updater updater = new Updater() { 713 public void update(Node newParent, Node oldNode, Node newNode) { 714 ((Element)newParent).setAttribute(name,value); 715 } 716 }; 717 return mutate(element, null, null, updater); 718 } 719 720 726 public synchronized List <Node> removeAttribute(Element element, final String name) { 727 Updater updater = new Updater() { 728 public void update(Node newParent, Node oldNode, Node newNode) { 729 ((Element)newParent).removeAttribute(name); 730 } 731 }; 732 return mutate(element, null, null, updater); 733 } 734 735 private interface CheckIOExceptionUpdater extends Updater { 736 public IOException getError(); 737 } 738 739 public synchronized List <Node> setXmlFragmentText(Element node, final String value) throws IOException { 740 CheckIOExceptionUpdater updater = new CheckIOExceptionUpdater() { 741 public void update(Node newParent, Node oldNode, Node newNode) { 742 try { 743 ((Element)newParent).setXmlFragmentText(value); 744 } catch(IOException ioe) { 745 error = ioe; 746 } 747 } 748 public IOException getError() { 749 return error; 750 } 751 private IOException error; 752 }; 753 List <Node> retPath = mutate(node, null, null, updater, MutationType.CHILDREN); 754 if (updater.getError() != null) { 755 throw updater.getError(); 756 } else { 757 return retPath; 758 } 759 } 760 761 public synchronized List <Node> setTextValue(Node node, String value) { 762 Node text = (Node) currentDocument.createTextNode(value); 763 Updater updater = new Updater() { 764 public void update(Node newParent, Node oldNode, Node newNode) { 765 while(newParent.hasChildNodes()) { 766 newParent.removeChild(newParent.getLastChild()); 767 } 768 newParent.appendChild(newNode); 769 } 770 }; 771 return mutate(node, null, text, updater); 772 } 773 774 783 private List <Node> updateAncestors(List <Node> ancestors, Node modifiedNode, Node originalNode) { 784 assert ancestors != null && modifiedNode != null && originalNode != null; 785 List <Node> newAncestors = new ArrayList <Node>(ancestors.size()); 786 Node currentModifiedNode = modifiedNode; 787 Node currentOrigNode = originalNode; 788 for(Node parentNode: ancestors) { 789 Node newParentNode = (Node)parentNode.clone(false,true,true); 790 newParentNode.replaceChild(currentModifiedNode, currentOrigNode); 791 newAncestors.add(newParentNode); 792 currentOrigNode = parentNode; 793 currentModifiedNode = newParentNode; 794 } 795 return newAncestors; 796 } 797 798 802 public synchronized Document getDocument() { 803 checkStableOrParsingState(); 804 return currentDocument; 805 } 806 807 811 public synchronized Document getCurrentDocument() { 812 return currentDocument; 813 } 814 815 820 synchronized void resetDocument(Document newDoc) { 821 try { 822 fireUndoEvents = false; 823 List <Difference> diffs = new NodeIdDiffFinder().findDiff(getCurrentDocument(), newDoc); 824 List <Difference> filtered = DiffFinder.filterWhitespace(diffs); 825 setDocument(newDoc); 827 if ( filtered != null && !filtered.isEmpty() ) { 828 fireDiffEvents(filtered); 829 } 830 } finally { 831 fireUndoEvents = true; 832 } 833 } 834 835 private void flushDocument(Document newDoc) { 836 checkStableState(); 837 UndoableEditListener uel = null; 838 BaseDocument d = getSwingDocument(); 839 final CompoundEdit ce = new CompoundEdit (); 840 try { 841 FlushVisitor flushvisitor = new FlushVisitor(); 842 String newXMLText = flushvisitor.flushModel(newDoc); 843 uel = new UndoableEditListener () { 844 public void undoableEditHappened(UndoableEditEvent e) { 845 ce.addEdit(e.getEdit()); 846 } 847 }; 848 d.addUndoableEditListener(uel); 849 Utils.replaceDocument(d, newXMLText); 850 } catch (BadLocationException ble) { 851 assert false; 852 } finally { 853 if (uel != null) { 854 d.removeUndoableEditListener(uel); 855 } 856 ce.end(); 857 for (UndoableEditListener l : ues.getUndoableEditListeners()) { 858 l.undoableEditHappened(new UndoableEditEvent (this,ce)); 859 } 860 } 861 } 862 863 private BaseDocument getSwingDocument() { 864 BaseDocument bd = (BaseDocument) 865 source.getLookup().lookup(BaseDocument.class); 866 return bd; 867 } 868 869 public synchronized void setDocument(Document newDoc) { 870 currentDocument = newDoc; 871 } 872 873 878 public synchronized Status getStatus() { 879 return status; 880 } 881 882 886 public synchronized void addUndoableEditListener(UndoableEditListener l) { 887 ues.addUndoableEditListener(l); 888 } 889 890 894 public synchronized void removeUndoableEditListener(UndoableEditListener l) { 895 ues.addUndoableEditListener(l); 896 } 897 898 902 public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) { 903 pcs.addPropertyChangeListener(pcl); 904 } 905 906 910 public synchronized void removePropertyChangeListener(PropertyChangeListener pcl) { 911 pcs.removePropertyChangeListener(pcl); 912 } 913 914 917 private synchronized Node findNode(int id) { 918 FindVisitor fv = new FindVisitor(); 919 return fv.find(getDocument(), id); 920 } 921 922 929 public enum Status {BROKEN, STABLE, UNPARSED, PARSING;} 931 932 private void fireUndoableEditEvent(Document newDoc, Document oldDoc) { 933 if (fireUndoEvents) { 934 assert newDoc != oldDoc; 935 UndoableEdit ee = new XDMModelUndoableEdit(oldDoc, newDoc, this); 936 UndoableEditEvent ue = new UndoableEditEvent (this, ee); 937 for (UndoableEditListener l:ues.getUndoableEditListeners()) { 938 l.undoableEditHappened(ue); 939 } 940 } 941 } 942 943 private void checkNodeInTree(Node n) { 944 if (n.isInTree()) { 945 throw new IllegalArgumentException ("newValue must not have been added to model"); } 947 } 948 949 private void checkStableState() { 950 if (getStatus() != Status.STABLE ) { 951 throw new IllegalStateException ("flush can only be called from STABLE STATE"); } 953 } 954 955 private void checkStableOrParsingState() { 956 if (getStatus() != Status.STABLE && getStatus() != Status.PARSING) { 957 throw new IllegalStateException ("The model is not initialized or is broken."); } 959 } 960 961 private void setStatus(Status s) { 962 status = s; 963 } 964 965 969 public int getNextNodeId() { 970 int nodeId = nodeCount; 971 nodeCount++; 972 return nodeId; 973 } 974 975 978 private void resetIdMeter() { 979 nodeCount = 1; 980 } 981 982 private boolean isPretty() { 983 return pretty; 984 } 985 986 public void setPretty(boolean print) { 987 pretty = print; 988 } 989 990 private void doPrettyPrint(Node newParent, Node newNode, Node oldParent) { 991 if ((getStatus() != Status.PARSING) && isPretty()) { 992 if(isSimpleContent(newParent)) { 996 return; 997 } 998 if(!indentInitialized) 999 initializeIndent(oldParent); 1000 String parentIndent = calculateNodeIndent(oldParent); 1001 if(!isPretty(newParent, newNode)) { int offset = 1; 1003 if(oldParent.getChildNodes().getLength() == 0) { 1015 newParent.insertBefore(createPrettyText( 1016 parentIndent+getIndentation()), newNode); 1017 offset++; 1018 } 1019 int index = ((NodeImpl)newParent).getIndexOfChild(newNode); 1020 if(index > 0) { 1021 Node oldText = (Node)newParent.getChildNodes().item(index-1); 1022 if(checkPrettyText(oldText)) { 1023 1040 Text newText = createPrettyText( 1041 parentIndent+getIndentation()); 1042 newParent.replaceChild(newText, oldText); 1043 } else { 1044 1060 newParent.insertBefore(createPrettyText( 1061 parentIndent+getIndentation()), newNode); 1062 offset++; 1063 } 1064 } 1065 Node ref = null; 1066 if((index+offset) < newParent.getChildNodes().getLength()) 1067 ref = (Node)newParent.getChildNodes().item((index+offset)); 1068 if(ref != null) { 1069 if(!checkPrettyText(ref)) { 1070 1088 newParent.insertBefore(createPrettyText( 1089 parentIndent+getIndentation()), ref); 1090 } 1091 } else { 1092 1108 newParent.appendChild(createPrettyText(parentIndent)); 1109 } 1110 } 1111 1112 doPrettyPrintRecursive(newNode, parentIndent, newParent); } 1115 } 1116 1117 1121 private void initializeIndent(final Node n) { 1122 String parentIndent = calculateNodeIndent(n); 1123 List <Node> pathToRoot = new PathFromRootVisitor().findPath(getDocument(), n); 1124 if(parentIndent.length() > 0 && pathToRoot.size()-2 > 0) { 1125 double step = Math.floor(parentIndent.length() / (double) (pathToRoot.size()-2)); 1127 StringBuffer sb = new StringBuffer (); 1128 for(int i=0;i<step;i++) 1129 sb.append(" "); 1130 String indentString = sb.toString(); 1131 if(indentString.length() > 0) 1132 setIndentation(indentString); 1133 else 1134 setDefaultIndentation(); 1135 } else 1136 setDefaultIndentation(); 1137 } 1138 1139 private String calculateNodeIndent(final Node n) { 1140 String indent = ""; 1141 Node parent = (Node) n.getParentNode(); 1142 if(parent != null) { 1143 int index = parent.getIndexOfChild(n); 1144 if(index > 0) { 1145 Node txt = (Node) parent.getChildNodes().item(index-1); 1146 if(checkPrettyText(txt)) { 1147 String wsValue = ((NodeImpl)txt).getTokens().get(0).getValue(); 1148 int ndx = wsValue.lastIndexOf("\n"); 1149 if(ndx != -1 && (ndx+1) < wsValue.length()) 1150 indent = wsValue.substring(ndx+1); 1151 } 1152 } 1153 } 1154 return indent; 1155 } 1156 1157 private void doPrettyPrintRecursive(Node n, String indent, Node parent) { 1158 if ((getStatus() != Status.PARSING) && isPretty()) { 1159 if(isSimpleContent(n)) 1160 return; else if(n instanceof Element && isPretty(n)) { fixPrettyForCopiedNode(n, indent, parent); 1163 } else { 1164 List <Node> childList = new ArrayList <Node>(); 1165 List <Node> visitList = new ArrayList <Node>(); 1166 NodeList childs = n.getChildNodes(); 1167 for(int i=0;i<childs.getLength();i++) { 1168 childList.add((Node)childs.item(i)); 1169 if(childs.item(i) instanceof Element) 1170 visitList.add((Node)childs.item(i)); 1171 } 1172 String parentIndent = indent+getIndentation(); 1173 if(childList.size() > 0) 1174 n.appendChild(createPrettyText(parentIndent)); 1175 String childIndent = parentIndent+getIndentation(); 1176 for(int i=childList.size()-1;i>=0;i--) { 1177 Node ref = (Node)childList.get(i); 1178 Text postText = createPrettyText(childIndent); 1179 n.insertBefore(postText, ref); 1180 } 1181 childList.clear(); for(int i=0;i<visitList.size();i++) { 1183 doPrettyPrintRecursive((Node)visitList.get((i)), parentIndent, n); 1184 } 1185 visitList.clear(); } 1187 } 1188 } 1189 1190 1194 private void fixPrettyForCopiedNode(Node n, String indent, Node parent) { 1195 NodeList childs = n.getChildNodes(); 1196 if(childs.getLength() == 0) 1197 return; 1198 Text nlastChild = (Text)childs.item(childs.getLength()-1); 1199 String lc = ((NodeImpl)nlastChild).getTokens().get(0).getValue(); 1200 NodeImpl pfirstChild = (NodeImpl)parent.getChildNodes().item(0); 1201 String fc = pfirstChild.getTokens().get(0).getValue(); 1202 1203 if(fc.length() == lc.length()) { return; 1205 } else { 1206 String parentIndent = indent+getIndentation(); 1207 String childIndent = parentIndent+getIndentation(); 1208 List <Node> childList = new ArrayList <Node>(); 1209 for(int i=0;i<childs.getLength();i++) { 1210 childList.add((Node)childs.item(i)); 1211 } 1212 for(int i=0;i<childList.size();i++) { 1213 Node txt = (Node)n.getChildNodes().item(i); 1214 if(checkPrettyText(txt)) { 1215 String newIndent = childIndent+getIndentation(); 1216 if(i==0) { 1217 newIndent = childIndent; 1218 } else if(i == childList.size()-1) { 1219 newIndent = parentIndent; 1220 } 1221 n.replaceChild(createPrettyText(newIndent), txt); 1222 } 1223 } 1224 for(int i=0;i<childList.size();i++) { 1225 fixPrettyForCopiedNode((Node)n.getChildNodes().item(i), 1226 childIndent, n); 1227 } 1228 childList.clear(); } 1230 } 1231 1232 private Text createPrettyText(String indent) { 1233 String textChars = "\n"+indent; 1234 Text txt = (Text)this.getDocument().createTextNode(textChars); 1235 return txt; 1236 } 1237 1238 private void undoPrettyPrint(Node newParent, Node oldNode, Node oldParent) { 1239 if ((getStatus() != Status.PARSING) && isPretty()) { 1240 String parentIndent = calculateNodeIndent(oldParent); 1241 int piLength = parentIndent != null ? parentIndent.length() : 0; 1242 int index = ((NodeImpl)oldParent).getIndexOfChild(oldNode); 1243 Node txtBefore = null; 1244 if(index > 0) { txtBefore = (Node)oldParent.getChildNodes().item(index-1); 1246 if(checkPrettyText(txtBefore) && 1247 piLength <= getLength((Text) txtBefore)) { 1248 1264 newParent.removeChild(txtBefore); 1265 } 1266 } 1267 if(newParent.getChildNodes().getLength() == 2 && 1268 index+1 < oldParent.getChildNodes().getLength()) { Node txtAfter = (Node)oldParent.getChildNodes().item(index+1); 1270 if(checkPrettyText(txtAfter) && 1271 piLength <= getLength((Text) txtAfter)) { 1272 1286 newParent.removeChild(txtAfter); 1287 } 1288 } 1289 } 1290 } 1291 1292 private int getLength(Text n) { 1293 int len = 0; 1294 for(Token token:((NodeImpl)n).getTokens()) 1295 len += token.getValue().length(); 1296 return len; 1297 } 1298 1299 private boolean checkPrettyText(Node txt) { 1300 if (txt instanceof Text) { 1301 if ((((NodeImpl)txt).getTokens().size() == 1) && 1302 isWhitespaceOnly(((NodeImpl)txt).getTokens().get(0).getValue())) { 1303 return true; 1304 } 1305 } 1306 return false; 1307 } 1308 1309 private boolean isPossibleWhiteSpace(String text) { 1310 return text.length() > 0 && 1311 Character.isWhitespace(text.charAt(0)) && 1312 Character.isWhitespace(text.charAt(text.length()-1)); 1313 } 1314 1315 private boolean isWhitespaceOnly(String tn) { 1316 return isPossibleWhiteSpace(tn) && 1317 tn.trim().length() == 0; 1318 } 1319 1320 public ElementIdentity getElementIdentity() { 1321 return eID; 1322 1323 1324 } 1325 1326 public void setElementIdentity(ElementIdentity eID) { 1327 this.eID = eID; 1328 } 1329 1330 private boolean isSimpleContent(Node newParent) { 1331 NodeList childs = newParent.getChildNodes(); 1332 for(int i=0;i<childs.getLength();i++) 1333 if(!(childs.item(i) instanceof Text)) 1334 return false; 1335 return true; 1336 } 1337 1338 private boolean isPretty(Node newParent) { 1339 return isPretty(newParent, null); 1340 } 1341 1342 private boolean isPretty(Node newParent, Node newNode) { 1343 boolean parentPretty = false; 1344 NodeList childs = newParent.getChildNodes(); 1345 int len = childs.getLength(); 1346 1347 1360 if(len == 0) 1361 parentPretty = true; 1362 else if(len == 1 && childs.item(0) instanceof Text) 1363 parentPretty = true; 1364 else if(len > 2 && 1365 checkPrettyText((Node) childs.item(0)) && checkPrettyText((Node) childs.item(len-1))) 1366 parentPretty = true; 1367 1368 if(!parentPretty) 1369 return false; 1370 1371 if(newNode != null) { 1372 Node preText = null; 1374 Node postText = null; 1375 int index = ((NodeImpl)newParent).getIndexOfChild(newNode); 1376 if(index > 0) 1377 preText = (Node)newParent.getChildNodes().item(index-1); 1378 if((index+1) < newParent.getChildNodes().getLength()) 1379 postText = (Node)newParent.getChildNodes().item((index+1)); 1380 1381 1394 if(checkPrettyText(preText) && checkPrettyText(postText)) 1395 return true; 1396 } else 1397 return parentPretty; 1398 1399 return false; 1400 } 1401 1402 1411 public void setQNameValuedAttributes(Map <QName ,List <QName >> attrsByElement) { 1412 qnameValuedAttributesByElementMap = attrsByElement; 1413 } 1414 public Map <QName ,List <QName >> getQNameValuedAttributes() { 1415 return qnameValuedAttributesByElementMap; 1416 } 1417 1418 1421 private XMLSyntaxParser parser; 1422 1423 1426 private Document currentDocument; 1427 1428 1431 private PropertyChangeSupport pcs; 1432 1433 1436 private ModelSource source; 1437 1438 1441 private Status status; 1442 1443 private boolean pretty = false; 1444 1445 1448 private UndoableEditSupport ues; 1449 1450 1453 private boolean fireUndoEvents = true; 1454 1455 1458 1461 public static final String PROP_MODIFIED = "modified"; 1462 1465 public static final String PROP_DELETED = "deleted"; 1466 1469 public static final String PROP_ADDED = "added"; 1470 1471 public static final String DEFAULT_INDENT = " "; 1472 1473 1476 private int nodeCount = 0; 1477 1478 private ElementIdentity eID; 1479 1480 private String currentIndent = ""; 1481 1482 private boolean indentInitialized = false; 1483 1484 private Map <QName ,List <QName >> qnameValuedAttributesByElementMap; 1485} 1486 | Popular Tags |