KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > structure > api > DocumentModel


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.editor.structure.api;
21
22 import java.lang.ref.WeakReference JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.Collection JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.Comparator JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Map JavaDoc;
32 import java.util.Set JavaDoc;
33 import java.util.SortedSet JavaDoc;
34 import java.util.Stack JavaDoc;
35 import java.util.TreeSet JavaDoc;
36 import java.util.WeakHashMap JavaDoc;
37 import javax.swing.SwingUtilities JavaDoc;
38 import javax.swing.event.DocumentEvent JavaDoc;
39 import javax.swing.event.DocumentEvent.EventType;
40 import javax.swing.event.DocumentListener JavaDoc;
41 import javax.swing.text.BadLocationException JavaDoc;
42 import javax.swing.text.Document JavaDoc;
43 import javax.swing.text.Position JavaDoc;
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 /**
54  * DocumentModel represents a hirarchical structure of a {@link javax.swing.text.Document}.
55  * It consists of a tree of DocumentElement objects which represents a various
56  * pieces of the document.
57  * <br>
58  * The model content is created by an implementation of the SPI class
59  * DocumentModelProvider responsible for parsing the text document a producing
60  * appropriate DocumentElement-s.
61  * For more information about how to create an implementation of the DocumentModelProvider
62  * and how to register to a specific file-type look into
63  * {@link org.netbeans.modules.editor.spi.DocumentModelProvider} documentation.
64  * <br>
65  * There can be only one instance of the model associated to a document
66  * instance at the time. Clients can obtain the instance
67  * through DocumentModel.getDocumentModel(Document doc) method.
68  * The client will obtain an instance of the model immediately, if the model
69  * already exists, or, when noone had asked for it, a new one will be created.
70  * <br>
71  * The model registers a DocumentListener to the associated Document and
72  * listen on its changes. When there is a change in the document,
73  * the model waits for a defined amount of time if another update happens,
74  * and if not, then asks DocumentModelProvider to regenerate the model's
75  * elements. If the document changes during the parsing process,
76  * the process will be stopped, all model changes thrown away,
77  * and a new update process will be started after the specified amount of time.
78  * The DocumentModelProvider obtains a list of document changes which
79  * happened during the 500ms interval and an instance of DocumentModelTransaction.
80  * The provider is then responsible to decide what parts of the document
81  * needs to be reparsed (based on what and where was changed in the document)
82  * and update the elements accordingly. During the parsing process the
83  * provider puts add, change and remove requests to the transaction.
84  * Once the provider finishes the parsing all the changes stored in the
85  * transactions are commited. Only during this process the content of the model
86  * is really updated. All event's (both from the model itself and from elements)
87  * are fired during the transaction commit, not during adding the requests
88  * into the transaction.
89  * <br>
90  * The model is read-only. All DocumentElement instancies are immutable and clients
91  * cannot modify them. Only DocumentModelProvider can modify the model.
92  * <br>
93  * DocumentElements cannot cross. There cannot be two elements with the same
94  * boundaries.
95  *
96  * <br>
97  * <b>Threading: Do not access the document model's element under the document writeLock()!!!</b>
98  * This may cause a deadlock if you do a document change, do not do writeUnlock() and access the model.
99  * In such case the model needs to resort it's elements which is done under document readLock().
100  *
101  *@author Marek Fukala
102  *@version 1.0
103  *
104  *@see DocumentElement
105  */

106 public final class DocumentModel {
107     
108     //after each document change update of the model is postponed for following timeout.
109
//if another document change happens in the time interval the update is postponed again.
110
private static int MODEL_UPDATE_TIMEOUT = 500; //milliseconds
111

112     //a Document instance which the model is build upon.
113
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 JavaDoc<DocumentElement> elements = new TreeSet JavaDoc<DocumentElement>(ELEMENTS_COMPARATOR);
122     
123     //stores a default root element
124
private DocumentElement rootElement;
125     
126     //the transaction is used to regenerate document model elements
127
//its non-null value states that there is an already running model update
128
private DocumentModel.DocumentModelModificationTransaction modelUpdateTransaction = null;
129 // private Object modelUpdateLock = new Object();
130

131     //a semaphore signalling the state of synchronization between document and the elements
132
//this is always se to true when the document is changed and is set back to false
133
//when the elements are resorted.
134
boolean documentDirty = true;
135     
136     private Hashtable JavaDoc<DocumentElement, List JavaDoc<DocumentElement>> childrenCache = null;
137     private Hashtable JavaDoc<DocumentElement, DocumentElement> parentsCache = null;
138     
139     //model synchronization
140
private int numReaders = 0;
141     private int numWriters = 0;
142     private Thread JavaDoc currWriter = null;
143     private Thread JavaDoc currReader = null;
144     
145     //stores DocumentModel listeners
146
private HashSet JavaDoc<DocumentModelListener> dmListeners = new HashSet JavaDoc<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 JavaDoc<Document JavaDoc, Object JavaDoc> locks = new WeakHashMap JavaDoc<Document JavaDoc, Object JavaDoc>();
153     
154     DocumentModel(Document JavaDoc doc, DocumentModelProvider provider) throws DocumentModelException {
155         this.doc = (BaseDocument)doc; //type changed in DocumentModel.getDocumentModel(document);
156
this.provider = provider;
157         
158         this.childrenCache = new Hashtable JavaDoc<DocumentElement, List JavaDoc<DocumentElement>>();
159         this.parentsCache = new Hashtable JavaDoc<DocumentElement, DocumentElement>();
160         
161         //init RP & RP task
162
requestProcessor = new RequestProcessor(DocumentModel.class.getName());
163         task = null;
164         
165         //create a new root element - this element comprises the entire document
166
addRootElement();
167         
168         /*create a sorted set which sorts its elements according to their
169         startoffsets and endoffsets.
170         - lets have elements E1 and E2:
171          
172         if E1.startOffset > E2.startOffset then the E1 is before E2.
173         if E1.startOffset == E2.startOffset then
174            if E1.endOffset > E2.endOffset then the E1 is before E2
175          */

176         initDocumentModel();
177         
178         this.changesWatcher = new DocumentChangesWatcher();
179         getDocument().addDocumentListener(WeakListeners.document(changesWatcher, doc));
180         
181     }
182     
183     /** Clients uses this method to obtain an instance of the model for a particullar text document.
184      * The client will either obtain an instance of the model immediately, if the model
185      * already exists, or, when noone had asked for the model, a new one will be created (which may take some time).
186      *
187      * @param doc the text document for which the client wants to create a model
188      * @return an initialized DocumentModel instance containing the structural data got from DocumentModelProvider
189      */

190     public static DocumentModel getDocumentModel(Document JavaDoc doc) throws DocumentModelException {
191         synchronized (getLock(doc)) {
192             if(!(doc instanceof BaseDocument))
193                 throw new ClassCastException JavaDoc("Currently it is necessary to pass org.netbeans.editor.BaseDocument instance into the DocumentModel.getDocumentProvider(j.s.t.Document) method.");
194             //first test if the document has already associated a document model
195
@SuppressWarnings JavaDoc("unchecked")
196             WeakReference JavaDoc<DocumentModel> modelWR = (WeakReference JavaDoc<DocumentModel>)doc.getProperty(DocumentModel.class);
197             DocumentModel cachedInstance = modelWR == null ? null : modelWR.get();
198             if(cachedInstance != null) {
199 // System.out.println("[document model] got from weak reference stored in editor document property");
200
return cachedInstance;
201             } else {
202                 //create a new modelx
203
Class JavaDoc editorKitClass = ((BaseDocument)doc).getKitClass();
204                 BaseKit kit = BaseKit.getKit(editorKitClass);
205                 if (kit != null) {
206                     String JavaDoc mimeType = kit.getContentType();
207                     //get the provider instance (the provider is a singleton class)
208
DocumentModelProvider provider =
209                             DocumentModelProviderFactory.getDefault().getDocumentModelProvider(mimeType);
210                     if(provider != null) {
211                         DocumentModel model = new DocumentModel(doc, provider);
212                         //and put it as a document property
213
doc.putProperty(DocumentModel.class, new WeakReference JavaDoc<DocumentModel>(model));
214 // System.out.println("[document model] created a new instance");
215
return model;
216                     } else
217                         return null; //no provider ??? should not happen?!?!
218
} else {
219                     throw new IllegalStateException JavaDoc("No editor kit for document " + doc + "!");
220                 }
221             }
222         }
223     }
224     
225     private static Object JavaDoc getLock(Document JavaDoc doc) {
226         synchronized (locks) {
227             Object JavaDoc lock = locks.get(doc);
228             if(lock == null) {
229                 lock = new Object JavaDoc();
230                 locks.put(doc, lock);
231             }
232             return lock;
233         }
234     }
235     
236     /** @return the text document this model is based upon */
237     public Document JavaDoc getDocument() {
238         return doc;
239     }
240     
241     /** Every model has at least one element - a root element.
242      * This element cannot be removed or manipulated somehow.
243      * All elements created by DocumentModelProvider are descendants of this element.
244      * <br>
245      * This is an entry point to the tree structure of the document.
246      * Use element.getChildren() to traverse the tree of elements.
247      *
248      * @return the root DocumentElement
249      */

250     public DocumentElement getRootElement() {
251         return rootElement;
252     }
253     
254     /** Adds an instance of DocumentModelListener to the model.*/
255     public void addDocumentModelListener(DocumentModelListener dml) {
256         dmListeners.add(dml);
257     }
258     
259     /** Removes an instance of DocumentModelListener from the model.*/
260     public void removeDocumentModelListener(DocumentModelListener dml) {
261         dmListeners.remove(dml);
262     }
263     
264     /** Decides whether the elements are in ancestor - descendant relationship.
265      * The relationship is defined as follows:
266      * isDescendant = ((ancestor.getStartOffset() < descendant.getStartOffset()) &&
267      * (ancestor.getEndOffset() > descendant.getEndOffset()));
268      * @return true if the ancestor element is an ancestor of the descendant element.
269      */

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 JavaDoc("ancestor == descendant!!!");
277             }
278             //there cannot normally by two elements with the same start or end offsets (boundaries)
279
//the only exception where startoffset and endoffset can be the some is root elements =>
280
if(ancestor == getRootElement()) return true;
281             
282             //performance optimalization
283
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     /** Returns a leaf element from the hierarchy which contains the
302      * given offset. If there is not such an element it returns root element.
303      * The element is returned as a leaf if it contains the offset and there
304      * isn't any children of the element containing the offset.
305      *
306      * @return the most top (leaf) element containing the offset.
307      */

308     public DocumentElement getLeafElementForOffset(int offset) {
309         readLock();
310         try{
311             if(getDocument().getLength() == 0) return getRootElement();
312             Iterator JavaDoc 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                         //a possible candidate found
319
if(de.getStartOffset() == de.getEndOffset() && de.getStartOffset() == offset) {
320                             //empty tag on the offset => return nearest candidate (its parent)
321
break;
322                         }
323                         leaf = de;
324                     }
325                 } else {
326                     //we have crossed the 'offset' => there cannot be a suitable
327
//element in the rest of the 'elements' set.
328
break;
329                 }
330             }
331             
332             //#69756 - there may be no found element in the case when the document content has been
333
//changed during the model update - then the documentmodel element's may be in inconsistent
334
//state so no element is found here. The correct approach to this is always lock the document
335
//for reading when updating the model. However from performance reasons it is not possible now.
336
if(leaf == null) leaf = getRootElement();
337             
338             return leaf;
339             
340         } finally {
341             readUnlock();
342         }
343     }
344     
345     // ---- private methods -----
346
static void setModelUpdateTimout(int timeout) {
347         MODEL_UPDATE_TIMEOUT = timeout;
348     }
349     
350     private synchronized TreeSet JavaDoc<DocumentElement> getElementsSet() {
351         if(documentDirty) resortAndMarkEmptyElements();
352         return elements;
353     }
354     
355     /** Returns a DocumentElement instance if there is such one with given boundaries. */
356     DocumentElement getDocumentElement(int startOffset, int endOffset) throws BadLocationException JavaDoc {
357         readLock();
358         try {
359             Iterator JavaDoc 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                 //we are far behind => there isn't such element => break the loop
367
if(de.getStartOffset() > startOffset) break;
368             }
369             
370             //nothing found
371
return null;
372             
373         }finally{
374             readUnlock();
375         }
376     }
377     
378     List JavaDoc<DocumentElement> getDocumentElements(int startOffset) throws BadLocationException JavaDoc {
379         readLock();
380         try {
381             ArrayList JavaDoc<DocumentElement> found = new ArrayList JavaDoc<DocumentElement>();
382             Iterator JavaDoc itr = getElementsSet().iterator();
383             while(itr.hasNext()) {
384                 DocumentElement de = (DocumentElement)itr.next();
385                 
386                 if(de.getStartOffset() == startOffset) found.add(de);
387                 
388                 //we are far behind => there isn't such element => break the loop
389
if(de.getStartOffset() > startOffset) break;
390             }
391             
392             //nothing found
393
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     //generate elements for the entire document
405
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         //test whether there is an already running model update and if so cancel it
417
if(modelUpdateTransaction != null) {
418             modelUpdateTransaction.setTransactionCancelled();
419             //wait until the transaction finishes
420
// synchronized (modelUpdateLock) {
421
// try {
422
// modelUpdateLock.wait();
423
// }catch(InterruptedException e) {
424
// //do nothing
425
// }
426
// }
427
}
428         
429         if(requestProcessor == null) return ;
430         //if there is an already scheduled update task cancel it and create a new one
431
if(task != null) task.cancel();
432         
433         Runnable JavaDoc modelUpdate = new Runnable JavaDoc() {
434             public void run() {
435                 updateModel();
436             }
437         };
438         
439         task = requestProcessor.post(modelUpdate, MODEL_UPDATE_TIMEOUT);
440     }
441     
442     private void updateModel() {
443         //create a new transaction
444
modelUpdateTransaction = createTransaction(false);
445         DocumentChange[] changes = changesWatcher.getDocumentChanges();
446         
447         if(debug) debugElements();
448         
449         try {
450             //let the model provider to decide what has changed and what to regenerate
451
provider.updateModel(modelUpdateTransaction, this, changes);
452             //commit all changes => update the model and fire events
453

454             //do that in EDT
455
try {
456                 SwingUtilities.invokeLater(new Runnable JavaDoc() {
457                     public void run() {
458                         try {
459                             writeLock(); //lock the model for reading
460
DocumentModel.this.modelUpdateTransaction.commit();
461                             //clear document changes cache -> if the transaction has been cancelled
462
//the cache is not cleared so next time the changes will be taken into account.
463
changesWatcher.clearChanges();
464                             modelUpdateTransaction = null; //states that the model update has already finished
465
}catch(DocumentModelTransactionCancelledException dmte) {
466                             //ignore
467
}catch(Exception JavaDoc e) {
468                             ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, e);
469                         }finally{
470                             writeUnlock(); //unlock the model
471
}
472                     }
473                 });
474             }catch(Exception JavaDoc ie) {
475                 ie.printStackTrace(); //XXX handle somehow
476
}
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     /** AFAIK there isn't also any way how to explicitly resort a set so I need to use a list and resort it
488      * manually after each elements change. This allows me to resort elements after a document change to
489      * keep correct he eleements order. */

490     private void resortAndMarkEmptyElements() {
491         //the resort has to lock the model for access since it modifies the elements order
492
//and the document for modifications
493
writeLock();
494         try {
495             doc.readLock();
496             try {
497                 ArrayList JavaDoc<DocumentElement> list = new ArrayList JavaDoc<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         //and clear children/parent caches
512
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 JavaDoc e) {
524             //this is very unlikely that the BLE will be thrown from this code
525
throw new IllegalStateException JavaDoc("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 JavaDoc<DocumentElement> getChildren(DocumentElement de) {
533         //try to use cached children if available
534
List JavaDoc<DocumentElement> cachedChildren = getCachedChildren(de);
535         if(cachedChildren != null) return cachedChildren;
536         
537         readLock();
538         try {
539             //test whether the element has been removed - in such a case anyone can still have a reference to it
540
//but the element is not held in the document structure elements list
541
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(); //do not cache
544
}
545             
546             //there is a problem with empty elements - if an element is removed its boundaries
547
//are the some and the standart getParent/getChildren algorith fails.
548
//the root element can be empty however it has to return children (also empty)
549
if(!de.isRootElement() && de.isEmpty()) return Collections.emptyList();
550             
551             //if the root element is empty the rest of elements is also empty and
552
//has to be returned as children
553
if(de.isRootElement() && de.isEmpty()) {
554                 ArrayList JavaDoc<DocumentElement> al =
555                         new ArrayList JavaDoc<DocumentElement>((Collection JavaDoc)getElementsSet().clone());
556                 al.remove(de); //remove the root itself
557
return al;
558             }
559             
560             ArrayList JavaDoc<DocumentElement> children = new ArrayList JavaDoc<DocumentElement>();
561             //get all elements with startOffset >= de.getStartOffset()
562
SortedSet JavaDoc tail = getElementsSet().tailSet(de);
563             //List tail = tailList(elements, de);
564

565             Iterator JavaDoc pchi = tail.iterator();
566             //skip the first element - this is the given element
567
pchi.next();
568             
569             //is there any other elements behind the 'de' element?
570
if(pchi.hasNext()) {
571                 //Since the elements are sorted acc. to their start and end offsets and elements cannot cross!!!
572
//the next element must be the first child if its startOffset < the given element endOffset
573
DocumentElement firstChild = (DocumentElement)pchi.next();
574                 children.add(firstChild);
575                 if(!isDescendantOf(de, firstChild)) return cacheChildrenList(de, Collections.<DocumentElement>emptyList());
576                 else {
577                     //the element is a child
578
//check the other elements - find first element which has startOffset > firstChild.endOffset
579
DocumentElement nextChild = firstChild;
580                     while(pchi.hasNext()) {
581                         DocumentElement docel = (DocumentElement)pchi.next();
582                         
583                         //test whether we didn't overpal the given 'de' endOffset
584
if(docel.getStartOffset() > de.getEndOffset()) break;
585                         
586                         //test if the element is the first next child which has startOffset > previous child endOffset
587
if(docel.getStartOffset() >= nextChild.getEndOffset() ) {
588                             //found a next child
589
children.add(docel);
590                             nextChild = docel;
591                         }
592                         
593                     }
594                 }
595             }
596             
597             //check whether I am returning myself as a child of me :-(
598
assert !children.contains(de) : "getChildren(de) contains the de itself!";
599             
600             return cacheChildrenList(de, children);
601         }catch(Exception JavaDoc e) {
602             System.err.println("Error in getCHildren!!!! for " + de);
603             debugElements();
604             DocumentModelUtils.dumpElementStructure(getRootElement());
605             e.printStackTrace();
606             //do not cache in case of error
607
return Collections.emptyList();
608         } finally {
609             readUnlock();
610         }
611     }
612     
613     private List JavaDoc<DocumentElement> getCachedChildren(DocumentElement de) {
614         return childrenCache.get(de);
615     }
616     
617     private List JavaDoc<DocumentElement> cacheChildrenList(DocumentElement de, List JavaDoc<DocumentElement> children) {
618         childrenCache.put(de, children);
619         return children;
620     }
621     
622     private void clearChildrenCache() {
623         childrenCache = new Hashtable JavaDoc<DocumentElement, List JavaDoc<DocumentElement>>();
624     }
625     
626     DocumentElement getParent(DocumentElement de) {
627         //try to use cached parent if available
628
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 JavaDoc("getParent() called for " + de + " which is not in the elements list!");
636             }
637             
638             if(de.isRootElement()) return null;
639             
640             //get all elements with startOffset <= de.getStartOffset()
641
SortedSet JavaDoc<DocumentElement> head = getElementsSet().headSet(de);
642             //List head = headList(elements, de);
643

644             if(head.isEmpty()) return null; //this should happen only for root element
645

646             DocumentElement[] headarr = head.toArray(new DocumentElement[]{});
647             //scan the elements in reversed order
648
for(int i = headarr.length - 1; i >= 0; i--) {
649                 DocumentElement el = headarr[i];
650                 //test whether the element is empty - if so, get next one etc...
651
//if(!isEmpty(el) && el.getStartOffset() < de.getStartOffset() && isDescendantOf(el,de)) return cacheParent(de, el);
652
if(!el.isEmpty() && isDescendantOf(el,de) && el.getStartOffset() < de.getStartOffset()) return cacheParent(de, el);
653             }
654             
655             //if not found (e.g. has the same startoffsets in case of root)
656
//root is returned in all cases except parent of the root itself
657
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 JavaDoc<DocumentElement, DocumentElement>();
675     }
676     
677     private void generateParentsCache() {
678         Stack JavaDoc<DocumentElement> path = new Stack JavaDoc<DocumentElement>();
679         for(DocumentElement de : (Set JavaDoc<DocumentElement>)getElementsSet()) {
680             if(path.empty()) {
681                 path.push(de); //ROOT element
682
} else {
683                 //find nearest ancestor
684
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     /** This method should be owerrided by subclasses to return appropriate DocumentElement
701      * instancies according to given DocumentElementType. */

702     private DocumentElement createDocumentElement(String JavaDoc name, String JavaDoc type, Map JavaDoc attributes,
703             int startOffset, int endOffset) throws BadLocationException JavaDoc {
704         //by default return DocumentElementBase
705
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     //-------------------------------------
722
// ------ model synchronization -------
723
//-------------------------------------
724

725     public synchronized final void readLock() {
726         try {
727             while (currWriter != null) {
728                 if (currWriter == Thread.currentThread()) {
729                     // writer has full read access.
730
return;
731                 }
732                 wait();
733             }
734             currReader = Thread.currentThread();
735             numReaders += 1;
736         } catch (InterruptedException JavaDoc e) {
737             throw new Error JavaDoc("Interrupted attempt to aquire read lock");
738         }
739     }
740     
741     public synchronized final void readUnlock() {
742         if (currWriter == Thread.currentThread()) {
743             // writer has full read access.
744
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                     //if this thread has readlock then we can write
761
return ;
762                 }
763                 wait();
764             }
765             currWriter = Thread.currentThread();
766             numWriters = 1;
767         } catch (InterruptedException JavaDoc e) {
768             throw new Error JavaDoc("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     //-------------------------------------
781
// --------- debug methods ------------
782
//-------------------------------------
783
void debugElements() {
784         System.out.println("DEBUG ELEMENTS:");
785         Iterator JavaDoc i = getElementsSet().iterator();
786         while(i.hasNext()) {
787             System.out.println(i.next());
788         }
789         //...and how our lovely elements looks sorted:
790
// System.out.println("\nSORTED:");
791
// ArrayList list = new ArrayList(elements);
792
// Collections.sort(list, ELEMENTS_COMPARATOR);
793
// i = list.iterator();
794
// while(i.hasNext()) {
795
// System.out.println(i.next());
796
// }
797
System.out.println("*****\n");
798     }
799     
800     //-------------------------------------
801
// --------- inner classes -------------
802
//-------------------------------------
803

804     /** Used by DocumentModelProvider to store planned changes in the model
805      * (adds/removes/content changes) of elements and then commit them all together.
806      * <br>
807      * The transaction can be cancelled, then any attempt to add or remove document
808      * element to/from the transaction causes the DocumentModelTransactionCancelledException
809      * exception to be thrown.
810      *
811      */

812     public final class DocumentModelModificationTransaction {
813         
814         private ArrayList JavaDoc<DocumentModelModification> modifications = new ArrayList JavaDoc<DocumentModelModification>();
815         private boolean transactionCancelled = false;
816         private boolean init;
817         
818         DocumentModelModificationTransaction(boolean init) {
819             this.init = init;
820         }
821         
822         /** Creates a new DocumentElement and adds it into the transaction.
823          *
824          * @param name the name of the DocumentElement
825          * @param type the type of the elemenent
826          * @param attributes the Map of element's attributes
827          * @param startOffset, endOffset the element's boundaries
828          * @throws DocumentModelTransactionCancelledException when the transaction has been cancelled and someone
829          * calls this method.
830          */

831         public DocumentElement addDocumentElement(String JavaDoc name, String JavaDoc type, Map JavaDoc attributes, int startOffset,
832                 int endOffset) throws BadLocationException JavaDoc, DocumentModelTransactionCancelledException {
833             //test if the transaction has been cancelled and if co throw TransactionCancelledException
834
if(transactionCancelled) throw new DocumentModelTransactionCancelledException();
835             
836 // if(startOffset == endOffset) {
837
// System.out.println("Warning: Adding an empty element into transaction!");
838
// return null;
839
// }
840

841             //create a new DocumentElement instance
842
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         /** Adds a remove request of an already existing DocumentElement to the transaction.
854          *
855          * @param de the document element to be removed
856          * @param removeAllItsDescendants causes that all the element's descendants will be removed from the model as well.
857          */

858         public void removeDocumentElement(DocumentElement de, boolean removeAllItsDescendants) throws DocumentModelTransactionCancelledException {
859             //test if the transaction has been cancelled and if co throw TransactionCancelledException
860
if(transactionCancelled) throw new DocumentModelTransactionCancelledException();
861             
862             //we cannot remove root element
863
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             //first remove children
870
if(removeAllItsDescendants) {
871                 //remove all its descendants recursivelly
872
Iterator JavaDoc/*<DocumentElement>*/ childrenIterator = getChildren(de).iterator();
873                 while(childrenIterator.hasNext()) {
874                     DocumentElement child = (DocumentElement)childrenIterator.next();
875                     removeDocumentElement(child, true);
876                 }
877             }
878             
879             //and then myself
880
DocumentModelModification dmm = new DocumentModelModification(de, DocumentModelModification.ELEMENT_REMOVED);
881             modifications.add(dmm);
882         }
883         
884         /** Adds a new text update request to the transaction.
885          *
886          * @param de the Document element which text content has been changed.
887          */

888         public void updateDocumentElementText(DocumentElement de) throws DocumentModelTransactionCancelledException {
889             //test if the transaction has been cancelled and if co throw TransactionCancelledException
890
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         /** Adds a attribs update request to the transaction.
897          *
898          * @param de the Document element which text content has been changed.
899          * @param attrs updated attributes
900          */

901         public void updateDocumentElementAttribs(DocumentElement de, Map JavaDoc attrs) throws DocumentModelTransactionCancelledException {
902             //test if the transaction has been cancelled and if co throw TransactionCancelledException
903
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                 //test if the transaction has been cancelled and if co throw TransactionCancelledException
915
if(transactionCancelled) throw new DocumentModelTransactionCancelledException();
916                 
917                 //XXX not an ideal algorithm :-)
918
//first remove all elements
919
long r = System.currentTimeMillis();
920                 if(debug) System.out.println("\n# commiting REMOVEs");
921                 Iterator JavaDoc<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                 //if the entire document content has been removed the root element is marked as empty
933
//to be able to add new elements inside we need to unmark it now
934
getRootElement().setElementIsEmptyState(false);
935                 
936                 long adds = System.currentTimeMillis();
937                 //then add all new elements
938
//it is better to add the elements from roots to leafs
939
if(debug) System.out.println("\n# commiting ADDs");
940                 mods = modifications.iterator();
941                 TreeSet JavaDoc<DocumentElement> sortedAdds = new TreeSet JavaDoc<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 JavaDoc<DocumentElement> reallyAdded = new ArrayList JavaDoc<DocumentElement>(sortedAdds.size());
948                 
949                 int addsNum = sortedAdds.size();
950                 Iterator JavaDoc<DocumentElement> addsIterator = sortedAdds.iterator();
951                 while(addsIterator.hasNext()) {
952                     DocumentElement de = addsIterator.next();
953                     if(addDE(de)) {
954                         reallyAdded.add(de);
955                     }
956                 }
957                 //clear caches after the elements has been added
958
clearChildrenCache();
959                 clearParentsCache();
960                 
961                 if(!init) { //do not fire events during model init
962
generateParentsCache();
963                     //fire add events for really added elements
964
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 // debugElements();
994
// DocumentModelUtils.dumpElementStructure(getRootElement());
995
}
996         
997         private void updateDEText(DocumentElement de) {
998             //notify model listeners
999
fireDocumentModelEvent(de, ELEMENT_CHANGED);
1000            //notify element listeners
1001
((DocumentElement)de).contentChanged();
1002        }
1003        
1004        private void updateDEAttrs(DocumentElement de, Map JavaDoc attrs) {
1005            //set the new attributes
1006
de.setAttributes(attrs);
1007            
1008            //notify model listeners
1009
fireDocumentModelEvent(de, ELEMENT_ATTRS_CHANGED);
1010            //notify element listeners
1011
((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 JavaDoc children = de.getChildren();
1021            DocumentElement parent = (DocumentElement)de.getParentElement();
1022            
1023                /* events firing:
1024                 * If the added element has a children, we have to fire remove event
1025                 * to their previous parents (it is the added element's parent)
1026                 * and fire add event to the added element for all its children
1027                 */

1028            
1029            if(parent != null) {//root element doesn't have any parent
1030
//fire add event for the new document element itself
1031
parent.childAdded(de);
1032                //fire events for all affected children
1033
Iterator JavaDoc/*<DocumentElement>*/ 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        //note: document change events are fired from the leafs to root
1046
private void removeDE(DocumentElement de) {
1047            if(debug) System.out.println("[DTM] removing " + de);
1048            DocumentElement parent = null;
1049            //remove the element itself. Do not do so if the element is root element
1050
if(de.isRootElement()) return ;
1051            
1052            //do not try to remove already removed element
1053
if(!getElementsSet().contains(de)) return ;
1054            
1055            //I need to get the parent before removing from the list!
1056
parent = getParent(de);
1057            
1058            //get children of the element to be removed
1059
Iterator JavaDoc/*<DocumentElement>*/ childrenIterator = de.getChildren().iterator();
1060            
1061            if(debug) System.out.println("[DMT] removed element " + de + " ;parent = " + parent);
1062            
1063                /* events firing:
1064                 * If the removed element had a children, we have to fire add event
1065                 * to the parent of the removed element for each child.
1066                 */

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            //clear caches
1074
clearChildrenCache();
1075            clearParentsCache();
1076            
1077            getElementsSet().remove(de);
1078            
1079            //fire events for all affected children
1080
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            //notify the parent element that one of its children has been removed
1088
if(parent != null) parent.childRemoved(de);
1089            
1090            
1091            
1092            fireDocumentModelEvent(de, ELEMENT_REMOVED);
1093        }
1094        
1095        /** called by the DocumentModel when the document changes during model update (the trans. lifetime) */
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 JavaDoc 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 JavaDoc attrs) {
1116                this(de, type);
1117                this.attrs = attrs;
1118            }
1119            
1120            public boolean equals(Object JavaDoc 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    /** This exception is thrown when someone tries to add a request to an already cancelled transaction.*/
1129    public final class DocumentModelTransactionCancelledException extends Exception JavaDoc {
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    //compares elements according to their start offsets
1142
//XXX - design - this comparator should be defined in DocumentElement class in the compareTo method!!!!
1143
private static final Comparator JavaDoc<DocumentElement> ELEMENTS_COMPARATOR = new Comparator JavaDoc<DocumentElement>() {
1144        public int compare(DocumentElement de1, DocumentElement de2) {
1145            //fastly handle root element comparing
1146
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                //different startOffsets
1153
return startOffsetDelta;
1154            else {
1155                //the elements has the same startoffsets - so we need
1156
//to compare them according to their endoffsets
1157
int endOffsetDelta = de2.getEndOffset() - de1.getEndOffset();
1158                if(endOffsetDelta != 0) {
1159                    return (de1.isEmpty() || de2.isEmpty()) ? -endOffsetDelta : endOffsetDelta;
1160                } else {
1161                    //because of TreeSet operations seems to use the comparator to test equality of elements
1162
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// //equality acc. to attribs causes problems with readding of elements in XMLDocumentModelProvider when changing attributes.
1169
int attrsComp = ((DocumentElement.Attributes)de1.getAttributes()).compareTo(de2.getAttributes());
1170                            if(attrsComp != 0) {
1171                                return attrsComp;
1172                            } else {
1173                                //Compare according to their hashCodes
1174
//the same element have the same hashcode
1175
//This is necessary when more elements with the same name is deleted
1176
//then they have the same start-end offsets so they "seem" to be
1177
//the same, thought they aren't.
1178
return de1.isEmpty() ? de2.hashCode() - de1.hashCode() : 0;
1179                            }
1180                        }
1181                    }
1182                }
1183            }
1184        }
1185        public boolean equals(Object JavaDoc obj) {
1186            return obj.equals(DocumentModel.ELEMENTS_COMPARATOR);
1187        }
1188    };
1189    
1190    private final class DocumentChangesWatcher implements DocumentListener JavaDoc {
1191        
1192        private ArrayList JavaDoc<DocumentChange> documentChanges = new ArrayList JavaDoc<DocumentChange>();
1193        
1194        public void changedUpdate(javax.swing.event.DocumentEvent JavaDoc documentEvent) {
1195            //no need to handle document attributes changes
1196
}
1197        
1198        public void insertUpdate(javax.swing.event.DocumentEvent JavaDoc documentEvent) {
1199            documentChanged(documentEvent);
1200        }
1201        
1202        public void removeUpdate(javax.swing.event.DocumentEvent JavaDoc documentEvent) {
1203            documentChanged(documentEvent);
1204        }
1205        
1206        //assumption: this method contains no locking AFAIK
1207
private void documentChanged(DocumentEvent JavaDoc documentEvent) {
1208            //indicate that the synchronization between document content and the element may be broken
1209
//not used by the model logic itself but by the "resortElements" method.
1210
documentDirty = true;
1211            
1212            try {
1213                //test whether a new text was inserted before or after the root element boundaries (positions)
1214
if(getRootElement().getStartOffset() > 0 || getRootElement().getEndOffset() < getDocument().getLength()) {
1215                    getRootElement().setStartPosition(0);
1216                    getRootElement().setEndPosition(getDocument().getLength());
1217                }
1218                
1219                //TODO: here we have to decide whether the document change affects
1220
//the model and how.
1221
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 JavaDoc e) {
1229                e.printStackTrace();
1230            }
1231            
1232            requestModelUpdate();
1233        }
1234        
1235        public DocumentChange[] getDocumentChanges() {
1236            List JavaDoc<DocumentChange> changes = (List JavaDoc<DocumentChange>)documentChanges.clone();
1237            return changes.toArray(new DocumentChange[]{});
1238        }
1239        
1240        public void clearChanges() {
1241            documentChanges.clear();
1242        }
1243        
1244    }
1245    
1246    /** A text document change wrapper similar to DocumentEvent. It stores the change type (remove/insert), its offset and length.
1247     * An array of these objects is passed into the DocumentModelProvider.updateModel().
1248     *
1249     * @see DocumentModel overall description for more infermation
1250     */

1251    public class DocumentChange {
1252        
1253        /** document text insert */
1254        public static final int INSERT=0;
1255        
1256        /** removal of a text from text document */
1257        public static final int REMOVE=1;
1258        
1259        private Position JavaDoc changeStart;
1260        private int changeLength, type;
1261        
1262        DocumentChange(Position JavaDoc changeStart, int changeLength, int type) {
1263            this.changeStart = changeStart;
1264            this.changeLength = changeLength;
1265            this.type = type;
1266        }
1267        
1268        /** @return a Position in the text document when the change started. */
1269        public Position JavaDoc getChangeStart() {
1270            return changeStart;
1271        }
1272        /** @return the length of the change. */
1273        public int getChangeLength() {
1274            return changeLength;
1275        }
1276        
1277        /** @return either DocumentChange.INSERT or DocumentChange.REMOVE */
1278        public int getChangeType() {
1279            return type;
1280        }
1281        
1282        public String JavaDoc toString() {
1283            return "Change["+getChangeStart().getOffset() + "-" + (getChangeStart().getOffset() + getChangeLength())+ "-" + (type == INSERT ? "INSERT" : "REMOVE") + "] text: " + getChangeText();
1284        }
1285        
1286        private String JavaDoc getChangeText() {
1287            try {
1288                String JavaDoc 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 JavaDoc e) {
1294                return "BadLocationException thrown: " + e.getMessage();
1295            }
1296            return null; //why do I need this???? :-)
1297
}
1298        
1299    }
1300    
1301//root document element - always present in the model - even in an empty one
1302
private static final String JavaDoc 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 JavaDoc GENERATING_MODEL_PROPERTY = "generating_document_model";
1308    
1309}
1310
Popular Tags