1 19 20 package org.netbeans.modules.editor.structure.api; 21 22 import java.lang.ref.WeakReference ; 23 import java.util.ArrayList ; 24 import java.util.Collection ; 25 import java.util.Collections ; 26 import java.util.Comparator ; 27 import java.util.HashSet ; 28 import java.util.Hashtable ; 29 import java.util.Iterator ; 30 import java.util.List ; 31 import java.util.Map ; 32 import java.util.Set ; 33 import java.util.SortedSet ; 34 import java.util.Stack ; 35 import java.util.TreeSet ; 36 import java.util.WeakHashMap ; 37 import javax.swing.SwingUtilities ; 38 import javax.swing.event.DocumentEvent ; 39 import javax.swing.event.DocumentEvent.EventType; 40 import javax.swing.event.DocumentListener ; 41 import javax.swing.text.BadLocationException ; 42 import javax.swing.text.Document ; 43 import javax.swing.text.Position ; 44 import org.netbeans.editor.BaseDocument; 45 import org.netbeans.editor.BaseKit; 46 import org.netbeans.modules.editor.structure.DocumentModelProviderFactory; 47 import org.netbeans.modules.editor.structure.spi.DocumentModelProvider; 48 import org.openide.ErrorManager; 49 import org.openide.util.RequestProcessor; 50 import org.openide.util.WeakListeners; 51 52 53 106 public final class DocumentModel { 107 108 private static int MODEL_UPDATE_TIMEOUT = 500; 112 private BaseDocument doc; 114 private DocumentModelProvider provider; 115 116 private DocumentChangesWatcher changesWatcher; 117 118 private RequestProcessor requestProcessor; 119 private RequestProcessor.Task task; 120 121 private TreeSet <DocumentElement> elements = new TreeSet <DocumentElement>(ELEMENTS_COMPARATOR); 122 123 private DocumentElement rootElement; 125 126 private DocumentModel.DocumentModelModificationTransaction modelUpdateTransaction = null; 129 131 boolean documentDirty = true; 135 136 private Hashtable <DocumentElement, List <DocumentElement>> childrenCache = null; 137 private Hashtable <DocumentElement, DocumentElement> parentsCache = null; 138 139 private int numReaders = 0; 141 private int numWriters = 0; 142 private Thread currWriter = null; 143 private Thread currReader = null; 144 145 private HashSet <DocumentModelListener> dmListeners = new HashSet <DocumentModelListener>(); 147 private static final int ELEMENT_ADDED = 1; 148 private static final int ELEMENT_REMOVED = 2; 149 private static final int ELEMENT_CHANGED = 3; 150 private static final int ELEMENT_ATTRS_CHANGED = 4; 151 152 private static Map <Document , Object > locks = new WeakHashMap <Document , Object >(); 153 154 DocumentModel(Document doc, DocumentModelProvider provider) throws DocumentModelException { 155 this.doc = (BaseDocument)doc; this.provider = provider; 157 158 this.childrenCache = new Hashtable <DocumentElement, List <DocumentElement>>(); 159 this.parentsCache = new Hashtable <DocumentElement, DocumentElement>(); 160 161 requestProcessor = new RequestProcessor(DocumentModel.class.getName()); 163 task = null; 164 165 addRootElement(); 167 168 176 initDocumentModel(); 177 178 this.changesWatcher = new DocumentChangesWatcher(); 179 getDocument().addDocumentListener(WeakListeners.document(changesWatcher, doc)); 180 181 } 182 183 190 public static DocumentModel getDocumentModel(Document doc) throws DocumentModelException { 191 synchronized (getLock(doc)) { 192 if(!(doc instanceof BaseDocument)) 193 throw new ClassCastException ("Currently it is necessary to pass org.netbeans.editor.BaseDocument instance into the DocumentModel.getDocumentProvider(j.s.t.Document) method."); 194 @SuppressWarnings ("unchecked") 196 WeakReference <DocumentModel> modelWR = (WeakReference <DocumentModel>)doc.getProperty(DocumentModel.class); 197 DocumentModel cachedInstance = modelWR == null ? null : modelWR.get(); 198 if(cachedInstance != null) { 199 return cachedInstance; 201 } else { 202 Class editorKitClass = ((BaseDocument)doc).getKitClass(); 204 BaseKit kit = BaseKit.getKit(editorKitClass); 205 if (kit != null) { 206 String mimeType = kit.getContentType(); 207 DocumentModelProvider provider = 209 DocumentModelProviderFactory.getDefault().getDocumentModelProvider(mimeType); 210 if(provider != null) { 211 DocumentModel model = new DocumentModel(doc, provider); 212 doc.putProperty(DocumentModel.class, new WeakReference <DocumentModel>(model)); 214 return model; 216 } else 217 return null; } else { 219 throw new IllegalStateException ("No editor kit for document " + doc + "!"); 220 } 221 } 222 } 223 } 224 225 private static Object getLock(Document doc) { 226 synchronized (locks) { 227 Object lock = locks.get(doc); 228 if(lock == null) { 229 lock = new Object (); 230 locks.put(doc, lock); 231 } 232 return lock; 233 } 234 } 235 236 237 public Document getDocument() { 238 return doc; 239 } 240 241 250 public DocumentElement getRootElement() { 251 return rootElement; 252 } 253 254 255 public void addDocumentModelListener(DocumentModelListener dml) { 256 dmListeners.add(dml); 257 } 258 259 260 public void removeDocumentModelListener(DocumentModelListener dml) { 261 dmListeners.remove(dml); 262 } 263 264 270 public boolean isDescendantOf(DocumentElement ancestor, DocumentElement descendant) { 271 readLock(); 272 try { 273 if(ancestor == descendant) { 274 if(debug) System.out.println("ERROR in " + ancestor); 275 debugElements(); 276 throw new IllegalArgumentException ("ancestor == descendant!!!"); 277 } 278 if(ancestor == getRootElement()) return true; 281 282 int ancestorSO = ancestor.getStartOffset(); 284 int descendantSO = descendant.getStartOffset(); 285 int ancestorEO = ancestor.getEndOffset(); 286 int descendantEO = descendant.getEndOffset(); 287 288 if(!descendant.isEmpty()) { 289 if((ancestorSO == descendantSO && ancestorEO > descendantEO) 290 || (ancestorEO == descendantEO && ancestorSO < descendantSO)) 291 return true; 292 } 293 294 return (ancestorSO < descendantSO && ancestorEO > descendantEO); 295 296 }finally{ 297 readUnlock(); 298 } 299 } 300 301 308 public DocumentElement getLeafElementForOffset(int offset) { 309 readLock(); 310 try{ 311 if(getDocument().getLength() == 0) return getRootElement(); 312 Iterator itr = getElementsSet().iterator(); 313 DocumentElement leaf = null; 314 while(itr.hasNext()) { 315 DocumentElement de = (DocumentElement)itr.next(); 316 if(de.getStartOffset() <= offset) { 317 if(de.getEndOffset() >=offset) { 318 if(de.getStartOffset() == de.getEndOffset() && de.getStartOffset() == offset) { 320 break; 322 } 323 leaf = de; 324 } 325 } else { 326 break; 329 } 330 } 331 332 if(leaf == null) leaf = getRootElement(); 337 338 return leaf; 339 340 } finally { 341 readUnlock(); 342 } 343 } 344 345 static void setModelUpdateTimout(int timeout) { 347 MODEL_UPDATE_TIMEOUT = timeout; 348 } 349 350 private synchronized TreeSet <DocumentElement> getElementsSet() { 351 if(documentDirty) resortAndMarkEmptyElements(); 352 return elements; 353 } 354 355 356 DocumentElement getDocumentElement(int startOffset, int endOffset) throws BadLocationException { 357 readLock(); 358 try { 359 Iterator itr = getElementsSet().iterator(); 360 while(itr.hasNext()) { 361 DocumentElement de = (DocumentElement)itr.next(); 362 if(de.getStartOffset() == startOffset && 363 de.getEndOffset() == endOffset) 364 return de; 365 366 if(de.getStartOffset() > startOffset) break; 368 } 369 370 return null; 372 373 }finally{ 374 readUnlock(); 375 } 376 } 377 378 List <DocumentElement> getDocumentElements(int startOffset) throws BadLocationException { 379 readLock(); 380 try { 381 ArrayList <DocumentElement> found = new ArrayList <DocumentElement>(); 382 Iterator itr = getElementsSet().iterator(); 383 while(itr.hasNext()) { 384 DocumentElement de = (DocumentElement)itr.next(); 385 386 if(de.getStartOffset() == startOffset) found.add(de); 387 388 if(de.getStartOffset() > startOffset) break; 390 } 391 392 return found; 394 395 }finally{ 396 readUnlock(); 397 } 398 } 399 400 private DocumentModel.DocumentModelModificationTransaction createTransaction(boolean init) { 401 return new DocumentModelModificationTransaction(init); 402 } 403 404 private void initDocumentModel() throws DocumentModelException { 406 try { 407 DocumentModel.DocumentModelModificationTransaction trans = createTransaction(true); 408 provider.updateModel(trans, this, new DocumentChange[]{new DocumentChange(getDocument().getStartPosition(), getDocument().getLength(), DocumentChange.INSERT)}); 409 trans.commit(); 410 }catch(DocumentModelTransactionCancelledException e) { 411 assert false : "We should never get here"; 412 } 413 } 414 415 private void requestModelUpdate() { 416 if(modelUpdateTransaction != null) { 418 modelUpdateTransaction.setTransactionCancelled(); 419 } 428 429 if(requestProcessor == null) return ; 430 if(task != null) task.cancel(); 432 433 Runnable modelUpdate = new Runnable () { 434 public void run() { 435 updateModel(); 436 } 437 }; 438 439 task = requestProcessor.post(modelUpdate, MODEL_UPDATE_TIMEOUT); 440 } 441 442 private void updateModel() { 443 modelUpdateTransaction = createTransaction(false); 445 DocumentChange[] changes = changesWatcher.getDocumentChanges(); 446 447 if(debug) debugElements(); 448 449 try { 450 provider.updateModel(modelUpdateTransaction, this, changes); 452 454 try { 456 SwingUtilities.invokeLater(new Runnable () { 457 public void run() { 458 try { 459 writeLock(); DocumentModel.this.modelUpdateTransaction.commit(); 461 changesWatcher.clearChanges(); 464 modelUpdateTransaction = null; }catch(DocumentModelTransactionCancelledException dmte) { 466 }catch(Exception e) { 468 ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e); 469 }finally{ 470 writeUnlock(); } 472 } 473 }); 474 }catch(Exception ie) { 475 ie.printStackTrace(); } 477 }catch(DocumentModelException e) { 478 if(debug) System.err.println("[DocumentModelUpdate] " + e.getMessage()); 479 }catch(DocumentModelTransactionCancelledException dmcte) { 480 if(debug) System.out.println("[document model] update transaction cancelled."); 481 } 482 483 if(debug) DocumentModelUtils.dumpElementStructure(getRootElement()); 484 } 485 486 487 490 private void resortAndMarkEmptyElements() { 491 writeLock(); 494 try { 495 doc.readLock(); 496 try { 497 ArrayList <DocumentElement> list = new ArrayList <DocumentElement>(elements); 498 elements.clear(); 499 for (DocumentElement de: list) { 500 if(isEmpty(de)) de.setElementIsEmptyState(true); 501 elements.add(de); 502 } 503 } finally { 504 doc.readUnlock(); 505 } 506 }finally { 507 writeUnlock(); 508 } 509 documentDirty = false; 510 511 clearChildrenCache(); 513 clearParentsCache(); 514 } 515 516 private void addRootElement() { 517 try { 518 DocumentModelModificationTransaction dmt = createTransaction(false); 519 this.rootElement = dmt.addDocumentElement("root", DOCUMENT_ROOT_ELEMENT_TYPE, Collections.EMPTY_MAP, 520 0, getDocument().getLength()); 521 this.rootElement.setRootElement(true); 522 dmt.commit(); 523 }catch(BadLocationException e) { 524 throw new IllegalStateException ("Adding of root document element failed - strange!"); 526 }catch(DocumentModelTransactionCancelledException dmtce) { 527 assert false : "We should never get here"; 528 } 529 } 530 531 532 List <DocumentElement> getChildren(DocumentElement de) { 533 List <DocumentElement> cachedChildren = getCachedChildren(de); 535 if(cachedChildren != null) return cachedChildren; 536 537 readLock(); 538 try { 539 if(!getElementsSet().contains(de)) { 542 if(debug) System.out.println("Warning: DocumentModel.getChildren(...) called for " + de + " which has already been removed!"); 543 return Collections.emptyList(); } 545 546 if(!de.isRootElement() && de.isEmpty()) return Collections.emptyList(); 550 551 if(de.isRootElement() && de.isEmpty()) { 554 ArrayList <DocumentElement> al = 555 new ArrayList <DocumentElement>((Collection )getElementsSet().clone()); 556 al.remove(de); return al; 558 } 559 560 ArrayList <DocumentElement> children = new ArrayList <DocumentElement>(); 561 SortedSet tail = getElementsSet().tailSet(de); 563 565 Iterator pchi = tail.iterator(); 566 pchi.next(); 568 569 if(pchi.hasNext()) { 571 DocumentElement firstChild = (DocumentElement)pchi.next(); 574 children.add(firstChild); 575 if(!isDescendantOf(de, firstChild)) return cacheChildrenList(de, Collections.<DocumentElement>emptyList()); 576 else { 577 DocumentElement nextChild = firstChild; 580 while(pchi.hasNext()) { 581 DocumentElement docel = (DocumentElement)pchi.next(); 582 583 if(docel.getStartOffset() > de.getEndOffset()) break; 585 586 if(docel.getStartOffset() >= nextChild.getEndOffset() ) { 588 children.add(docel); 590 nextChild = docel; 591 } 592 593 } 594 } 595 } 596 597 assert !children.contains(de) : "getChildren(de) contains the de itself!"; 599 600 return cacheChildrenList(de, children); 601 }catch(Exception e) { 602 System.err.println("Error in getCHildren!!!! for " + de); 603 debugElements(); 604 DocumentModelUtils.dumpElementStructure(getRootElement()); 605 e.printStackTrace(); 606 return Collections.emptyList(); 608 } finally { 609 readUnlock(); 610 } 611 } 612 613 private List <DocumentElement> getCachedChildren(DocumentElement de) { 614 return childrenCache.get(de); 615 } 616 617 private List <DocumentElement> cacheChildrenList(DocumentElement de, List <DocumentElement> children) { 618 childrenCache.put(de, children); 619 return children; 620 } 621 622 private void clearChildrenCache() { 623 childrenCache = new Hashtable <DocumentElement, List <DocumentElement>>(); 624 } 625 626 DocumentElement getParent(DocumentElement de) { 627 DocumentElement cachedParent = getCachedParent(de); 629 if(cachedParent != null) return cachedParent; 630 631 readLock(); 632 try { 633 if(!getElementsSet().contains(de)) { 634 debugElements(); 635 throw new IllegalArgumentException ("getParent() called for " + de + " which is not in the elements list!"); 636 } 637 638 if(de.isRootElement()) return null; 639 640 SortedSet <DocumentElement> head = getElementsSet().headSet(de); 642 644 if(head.isEmpty()) return null; 646 DocumentElement[] headarr = head.toArray(new DocumentElement[]{}); 647 for(int i = headarr.length - 1; i >= 0; i--) { 649 DocumentElement el = headarr[i]; 650 if(!el.isEmpty() && isDescendantOf(el,de) && el.getStartOffset() < de.getStartOffset()) return cacheParent(de, el); 653 } 654 655 return cacheParent(de, getRootElement()); 658 659 }finally{ 660 readUnlock(); 661 } 662 } 663 664 private DocumentElement getCachedParent(DocumentElement de) { 665 return parentsCache.get(de); 666 } 667 668 private DocumentElement cacheParent(DocumentElement de, DocumentElement parent) { 669 parentsCache.put(de, parent); 670 return parent; 671 } 672 673 private void clearParentsCache() { 674 parentsCache = new Hashtable <DocumentElement, DocumentElement>(); 675 } 676 677 private void generateParentsCache() { 678 Stack <DocumentElement> path = new Stack <DocumentElement>(); 679 for(DocumentElement de : (Set <DocumentElement>)getElementsSet()) { 680 if(path.empty()) { 681 path.push(de); } else { 683 DocumentElement ancestor = path.pop(); 685 do { 686 if(isDescendantOf(ancestor, de)) { 687 cacheParent(de, ancestor); 688 path.push(ancestor); 689 path.push(de); 690 break; 691 } else { 692 ancestor = path.pop(); 693 } 694 } while(true); 695 } 696 } 697 } 698 699 700 702 private DocumentElement createDocumentElement(String name, String type, Map attributes, 703 int startOffset, int endOffset) throws BadLocationException { 704 return new DocumentElement(name, type, attributes, startOffset, endOffset, this ); 706 } 707 708 709 710 private void fireDocumentModelEvent(DocumentElement de, int type) { 711 for (DocumentModelListener cl: dmListeners) { 712 switch(type) { 713 case ELEMENT_ADDED: cl.documentElementAdded(de);break; 714 case ELEMENT_REMOVED: cl.documentElementRemoved(de);break; 715 case ELEMENT_CHANGED: cl.documentElementChanged(de);break; 716 case ELEMENT_ATTRS_CHANGED: cl.documentElementAttributesChanged(de);break; 717 } 718 } 719 } 720 721 725 public synchronized final void readLock() { 726 try { 727 while (currWriter != null) { 728 if (currWriter == Thread.currentThread()) { 729 return; 731 } 732 wait(); 733 } 734 currReader = Thread.currentThread(); 735 numReaders += 1; 736 } catch (InterruptedException e) { 737 throw new Error ("Interrupted attempt to aquire read lock"); 738 } 739 } 740 741 public synchronized final void readUnlock() { 742 if (currWriter == Thread.currentThread()) { 743 return; 745 } 746 assert numReaders > 0 : "Bad read lock state!"; 747 numReaders -= 1; 748 if(numReaders == 0) currReader = null; 749 notify(); 750 } 751 752 private synchronized final void writeLock() { 753 try { 754 while ((numReaders > 0) || (currWriter != null)) { 755 if (Thread.currentThread() == currWriter) { 756 numWriters++; 757 return; 758 } 759 if (Thread.currentThread() == currReader) { 760 return ; 762 } 763 wait(); 764 } 765 currWriter = Thread.currentThread(); 766 numWriters = 1; 767 } catch (InterruptedException e) { 768 throw new Error ("Interrupted attempt to aquire write lock"); 769 } 770 } 771 772 private synchronized final void writeUnlock() { 773 if (--numWriters <= 0) { 774 numWriters = 0; 775 currWriter = null; 776 notifyAll(); 777 } 778 } 779 780 void debugElements() { 784 System.out.println("DEBUG ELEMENTS:"); 785 Iterator i = getElementsSet().iterator(); 786 while(i.hasNext()) { 787 System.out.println(i.next()); 788 } 789 System.out.println("*****\n"); 798 } 799 800 804 812 public final class DocumentModelModificationTransaction { 813 814 private ArrayList <DocumentModelModification> modifications = new ArrayList <DocumentModelModification>(); 815 private boolean transactionCancelled = false; 816 private boolean init; 817 818 DocumentModelModificationTransaction(boolean init) { 819 this.init = init; 820 } 821 822 831 public DocumentElement addDocumentElement(String name, String type, Map attributes, int startOffset, 832 int endOffset) throws BadLocationException , DocumentModelTransactionCancelledException { 833 if(transactionCancelled) throw new DocumentModelTransactionCancelledException(); 835 836 841 DocumentElement de = createDocumentElement(name, type, attributes, startOffset, endOffset); 843 844 if(!getElementsSet().contains(de)) { 845 if(debug) System.out.println("# ADD " + de + " adding into transaction"); 846 DocumentModelModification dmm = new DocumentModelModification(de, DocumentModelModification.ELEMENT_ADD); 847 modifications.add(dmm); 848 } 849 850 return de; 851 } 852 853 858 public void removeDocumentElement(DocumentElement de, boolean removeAllItsDescendants) throws DocumentModelTransactionCancelledException { 859 if(transactionCancelled) throw new DocumentModelTransactionCancelledException(); 861 862 if(de.isRootElement()) { 864 if(debug) System.out.println("WARNING: root element cannot be removed!"); 865 return ; 866 } 867 if(debug) System.out.println("# REMOVE " + de + " adding into transaction "); 868 869 if(removeAllItsDescendants) { 871 Iterator childrenIterator = getChildren(de).iterator(); 873 while(childrenIterator.hasNext()) { 874 DocumentElement child = (DocumentElement)childrenIterator.next(); 875 removeDocumentElement(child, true); 876 } 877 } 878 879 DocumentModelModification dmm = new DocumentModelModification(de, DocumentModelModification.ELEMENT_REMOVED); 881 modifications.add(dmm); 882 } 883 884 888 public void updateDocumentElementText(DocumentElement de) throws DocumentModelTransactionCancelledException { 889 if(transactionCancelled) throw new DocumentModelTransactionCancelledException(); 891 892 DocumentModelModification dmm = new DocumentModelModification(de, DocumentModelModification.ELEMENT_CHANGED); 893 if(!modifications.contains(dmm)) modifications.add(dmm); 894 } 895 896 901 public void updateDocumentElementAttribs(DocumentElement de, Map attrs) throws DocumentModelTransactionCancelledException { 902 if(transactionCancelled) throw new DocumentModelTransactionCancelledException(); 904 905 DocumentModelModification dmm = new DocumentModelModification(de, DocumentModelModification.ELEMENT_ATTRS_CHANGED, attrs); 906 if(!modifications.contains(dmm)) modifications.add(dmm); 907 } 908 909 910 private void commit() throws DocumentModelTransactionCancelledException { 911 long a = System.currentTimeMillis(); 912 writeLock(); 913 try { 914 if(transactionCancelled) throw new DocumentModelTransactionCancelledException(); 916 917 long r = System.currentTimeMillis(); 920 if(debug) System.out.println("\n# commiting REMOVEs"); 921 Iterator <DocumentModelModification> mods = modifications.iterator(); 922 int removes = 0; 923 while(mods.hasNext()) { 924 DocumentModelModification dmm = mods.next(); 925 if(dmm.type == DocumentModelModification.ELEMENT_REMOVED) { 926 removeDE(dmm.de); 927 removes++; 928 } 929 } 930 if(measure) System.out.println("[xmlmodel] "+ removes + " removes commited in " + (System.currentTimeMillis() - r)); 931 932 getRootElement().setElementIsEmptyState(false); 935 936 long adds = System.currentTimeMillis(); 937 if(debug) System.out.println("\n# commiting ADDs"); 940 mods = modifications.iterator(); 941 TreeSet <DocumentElement> sortedAdds = new TreeSet <DocumentElement>(ELEMENTS_COMPARATOR); 942 while(mods.hasNext()) { 943 DocumentModelModification dmm = mods.next(); 944 if(dmm.type == DocumentModelModification.ELEMENT_ADD) sortedAdds.add(dmm.de); 945 } 946 947 ArrayList <DocumentElement> reallyAdded = new ArrayList <DocumentElement>(sortedAdds.size()); 948 949 int addsNum = sortedAdds.size(); 950 Iterator <DocumentElement> addsIterator = sortedAdds.iterator(); 951 while(addsIterator.hasNext()) { 952 DocumentElement de = addsIterator.next(); 953 if(addDE(de)) { 954 reallyAdded.add(de); 955 } 956 } 957 clearChildrenCache(); 959 clearParentsCache(); 960 961 if(!init) { generateParentsCache(); 963 for (DocumentElement de: reallyAdded) { 965 fireElementAddedEvent(de); 966 } 967 } 968 969 if(measure) System.out.println("[xmlmodel] " + addsNum + " adds commited in " + (System.currentTimeMillis() - adds)); 970 971 long upds = System.currentTimeMillis(); 972 if(debug) System.out.println("\n# commiting text UPDATESs"); 973 mods = modifications.iterator(); 974 while(mods.hasNext()) { 975 DocumentModelModification dmm = mods.next(); 976 if(dmm.type == DocumentModelModification.ELEMENT_CHANGED) updateDEText(dmm.de); 977 } 978 979 if(debug) System.out.println("\n# commiting attribs UPDATESs"); 980 mods = modifications.iterator(); 981 while(mods.hasNext()) { 982 DocumentModelModification dmm = mods.next(); 983 if(dmm.type == DocumentModelModification.ELEMENT_ATTRS_CHANGED) updateDEAttrs(dmm.de, dmm.attrs); 984 } 985 986 if(measure) System.out.println("[xmlmodel] updates commit done in " + (System.currentTimeMillis() - upds)); 987 } finally { 988 writeUnlock(); 989 } 990 if(debug) System.out.println("# commit finished\n"); 991 if(measure) System.out.println("[xmlmodel] commit done in " + (System.currentTimeMillis() - a)); 992 993 } 996 997 private void updateDEText(DocumentElement de) { 998 fireDocumentModelEvent(de, ELEMENT_CHANGED); 1000 ((DocumentElement)de).contentChanged(); 1002 } 1003 1004 private void updateDEAttrs(DocumentElement de, Map attrs) { 1005 de.setAttributes(attrs); 1007 1008 fireDocumentModelEvent(de, ELEMENT_ATTRS_CHANGED); 1010 ((DocumentElement)de).attributesChanged(); 1012 } 1013 1014 1015 private boolean addDE(DocumentElement de) { 1016 return getElementsSet().add(de); 1017 } 1018 1019 private void fireElementAddedEvent(DocumentElement de) { 1020 List children = de.getChildren(); 1021 DocumentElement parent = (DocumentElement)de.getParentElement(); 1022 1023 1028 1029 if(parent != null) { parent.childAdded(de); 1032 Iterator childrenIterator = children.iterator(); 1034 while(childrenIterator.hasNext()) { 1035 DocumentElement child = (DocumentElement)childrenIterator.next(); 1036 parent.childRemoved(child); 1037 de.childAdded(child); 1038 } 1039 } 1040 fireDocumentModelEvent(de, ELEMENT_ADDED); 1041 } 1042 1043 1044 1045 private void removeDE(DocumentElement de) { 1047 if(debug) System.out.println("[DTM] removing " + de); 1048 DocumentElement parent = null; 1049 if(de.isRootElement()) return ; 1051 1052 if(!getElementsSet().contains(de)) return ; 1054 1055 parent = getParent(de); 1057 1058 Iterator childrenIterator = de.getChildren().iterator(); 1060 1061 if(debug) System.out.println("[DMT] removed element " + de + " ;parent = " + parent); 1062 1063 1067 if(parent == null) { 1068 if(debug) System.out.println("[DTM] WARNING: element has no parent (no events are fired to it!!!) " + de); 1069 if(debug) System.out.println("[DTM] Trying to recover by returning root element..."); 1070 parent = getRootElement(); 1071 } 1072 1073 clearChildrenCache(); 1075 clearParentsCache(); 1076 1077 getElementsSet().remove(de); 1078 1079 while(childrenIterator.hasNext()) { 1081 DocumentElement child = (DocumentElement)childrenIterator.next(); 1082 if(debug) System.out.println("switching child " + child + "from removed " + de + "to parent " + parent); 1083 de.childRemoved(child); 1084 parent.childAdded(child); 1085 } 1086 1087 if(parent != null) parent.childRemoved(de); 1089 1090 1091 1092 fireDocumentModelEvent(de, ELEMENT_REMOVED); 1093 } 1094 1095 1096 private void setTransactionCancelled() { 1097 transactionCancelled = true; 1098 } 1099 1100 private final class DocumentModelModification { 1101 public static final int ELEMENT_ADD = 1; 1102 public static final int ELEMENT_REMOVED = 2; 1103 public static final int ELEMENT_CHANGED = 3; 1104 public static final int ELEMENT_ATTRS_CHANGED = 4; 1105 1106 public int type; 1107 public DocumentElement de; 1108 public Map attrs = null; 1109 1110 public DocumentModelModification(DocumentElement de, int type) { 1111 this.de = de; 1112 this.type = type; 1113 } 1114 1115 public DocumentModelModification(DocumentElement de, int type, Map attrs) { 1116 this(de, type); 1117 this.attrs = attrs; 1118 } 1119 1120 public boolean equals(Object o) { 1121 if(!(o instanceof DocumentModelModification)) return false; 1122 DocumentModelModification dmm = (DocumentModelModification)o; 1123 return (dmm.type == this.type) && (dmm.de.equals(this.de)); 1124 } 1125 } 1126 } 1127 1128 1129 public final class DocumentModelTransactionCancelledException extends Exception { 1130 1131 public DocumentModelTransactionCancelledException() { 1132 super(); 1133 } 1134 1135 } 1136 1137 static final boolean isEmpty(DocumentElement de) { 1138 return de.getStartOffset() == de.getEndOffset(); 1139 } 1140 1141 private static final Comparator <DocumentElement> ELEMENTS_COMPARATOR = new Comparator <DocumentElement>() { 1144 public int compare(DocumentElement de1, DocumentElement de2) { 1145 if(de1.isRootElement() && !de2.isRootElement()) return -1; 1147 if(!de1.isRootElement() && de2.isRootElement()) return +1; 1148 if(de2.isRootElement() && de1.isRootElement()) return 0; 1149 1150 int startOffsetDelta = de1.getStartOffset() - de2.getStartOffset(); 1151 if(startOffsetDelta != 0) 1152 return startOffsetDelta; 1154 else { 1155 int endOffsetDelta = de2.getEndOffset() - de1.getEndOffset(); 1158 if(endOffsetDelta != 0) { 1159 return (de1.isEmpty() || de2.isEmpty()) ? -endOffsetDelta : endOffsetDelta; 1160 } else { 1161 int typesDelta = de1.getType().compareTo(de2.getType()); 1163 if(typesDelta != 0) return typesDelta; 1164 else { 1165 int namesDelta = de1.getName().compareTo(de2.getName()); 1166 if(namesDelta != 0) return namesDelta; 1167 else { 1168 int attrsComp = ((DocumentElement.Attributes)de1.getAttributes()).compareTo(de2.getAttributes()); 1170 if(attrsComp != 0) { 1171 return attrsComp; 1172 } else { 1173 return de1.isEmpty() ? de2.hashCode() - de1.hashCode() : 0; 1179 } 1180 } 1181 } 1182 } 1183 } 1184 } 1185 public boolean equals(Object obj) { 1186 return obj.equals(DocumentModel.ELEMENTS_COMPARATOR); 1187 } 1188 }; 1189 1190 private final class DocumentChangesWatcher implements DocumentListener { 1191 1192 private ArrayList <DocumentChange> documentChanges = new ArrayList <DocumentChange>(); 1193 1194 public void changedUpdate(javax.swing.event.DocumentEvent documentEvent) { 1195 } 1197 1198 public void insertUpdate(javax.swing.event.DocumentEvent documentEvent) { 1199 documentChanged(documentEvent); 1200 } 1201 1202 public void removeUpdate(javax.swing.event.DocumentEvent documentEvent) { 1203 documentChanged(documentEvent); 1204 } 1205 1206 private void documentChanged(DocumentEvent documentEvent) { 1208 documentDirty = true; 1211 1212 try { 1213 if(getRootElement().getStartOffset() > 0 || getRootElement().getEndOffset() < getDocument().getLength()) { 1215 getRootElement().setStartPosition(0); 1216 getRootElement().setEndPosition(getDocument().getLength()); 1217 } 1218 1219 int change_offset = documentEvent.getOffset(); 1222 int change_length = documentEvent.getLength(); 1223 1224 int type = documentEvent.getType().equals(EventType.REMOVE) ? DocumentChange.REMOVE : DocumentChange.INSERT; 1225 DocumentChange dchi = new DocumentChange(getDocument().createPosition(change_offset), change_length, type); 1226 documentChanges.add(dchi); 1227 if(debug) System.out.println(dchi); 1228 }catch(BadLocationException e) { 1229 e.printStackTrace(); 1230 } 1231 1232 requestModelUpdate(); 1233 } 1234 1235 public DocumentChange[] getDocumentChanges() { 1236 List <DocumentChange> changes = (List <DocumentChange>)documentChanges.clone(); 1237 return changes.toArray(new DocumentChange[]{}); 1238 } 1239 1240 public void clearChanges() { 1241 documentChanges.clear(); 1242 } 1243 1244 } 1245 1246 1251 public class DocumentChange { 1252 1253 1254 public static final int INSERT=0; 1255 1256 1257 public static final int REMOVE=1; 1258 1259 private Position changeStart; 1260 private int changeLength, type; 1261 1262 DocumentChange(Position changeStart, int changeLength, int type) { 1263 this.changeStart = changeStart; 1264 this.changeLength = changeLength; 1265 this.type = type; 1266 } 1267 1268 1269 public Position getChangeStart() { 1270 return changeStart; 1271 } 1272 1273 public int getChangeLength() { 1274 return changeLength; 1275 } 1276 1277 1278 public int getChangeType() { 1279 return type; 1280 } 1281 1282 public String toString() { 1283 return "Change["+getChangeStart().getOffset() + "-" + (getChangeStart().getOffset() + getChangeLength())+ "-" + (type == INSERT ? "INSERT" : "REMOVE") + "] text: " + getChangeText(); 1284 } 1285 1286 private String getChangeText() { 1287 try { 1288 String text = getDocument().getText(getChangeStart().getOffset(), getChangeLength()); 1289 if(type == INSERT) return text; 1290 else if(type == REMOVE) return "[cannot provide removed text]; the text on remove offset: " + text; 1291 1292 assert false : "Wrong document change type!"; 1293 }catch(BadLocationException e) { 1294 return "BadLocationException thrown: " + e.getMessage(); 1295 } 1296 return null; } 1298 1299 } 1300 1301 private static final String DOCUMENT_ROOT_ELEMENT_TYPE = "ROOT_ELEMENT"; 1303 1304 private static final boolean debug = Boolean.getBoolean("org.netbeans.editor.model.debug"); 1305 private static final boolean measure = Boolean.getBoolean("org.netbeans.editor.model.measure"); 1306 1307 private static final String GENERATING_MODEL_PROPERTY = "generating_document_model"; 1308 1309} 1310 | Popular Tags |