* Updates any document structure as a result of text removal. This * method is called after the text has been removed from the Content. * This will happen within a write lock. If a subclass * of this class reimplements this method, it should delegate to the * superclass as well. * * @param chng a description of the change */ protected void postRemoveUpdate(DefaultDocumentEvent chng) { if( getProperty(I18NProperty).equals( Boolean.TRUE ) ) updateBidi( chng ); } /** * Update the bidi element structure as a result of the given change * to the document. The given change will be updated to reflect the * changes made to the bidi structure. * * This method assumes that every offset in the model is contained in * exactly one paragraph. This method also assumes that it is called * after the change is made to the default element structure. */ void updateBidi( DefaultDocumentEvent chng ) { // Calculate the range of paragraphs affected by the change. int firstPStart; int lastPEnd; if( chng.type == DocumentEvent.EventType.INSERT || chng.type == DocumentEvent.EventType.CHANGE ) { int chngStart = chng.getOffset(); int chngEnd = chngStart + chng.getLength(); firstPStart = getParagraphElement(chngStart).getStartOffset(); lastPEnd = getParagraphElement(chngEnd).getEndOffset(); } else if( chng.type == DocumentEvent.EventType.REMOVE ) { Element paragraph = getParagraphElement( chng.getOffset() ); firstPStart = paragraph.getStartOffset(); lastPEnd = paragraph.getEndOffset(); } else { throw new Error("Internal error: unknown event type."); } //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd ); // Calculate the bidi levels for the affected range of paragraphs. The // levels array will contain a bidi level for each character in the // affected text. byte levels[] = calculateBidiLevels( firstPStart, lastPEnd ); Vector<Element> newElements = new Vector<Element>(); // Calculate the first span of characters in the affected range with // the same bidi level. If this level is the same as the level of the // previous bidi element (the existing bidi element containing // firstPStart-1), then merge in the previous element. If not, but // the previous element overlaps the affected range, truncate the // previous element at firstPStart. int firstSpanStart = firstPStart; int removeFromIndex = 0; if( firstSpanStart > 0 ) { int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1); removeFromIndex = prevElemIndex; Element prevElem = bidiRoot.getElement(prevElemIndex); int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes()); //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]); if( prevLevel==levels[0] ) { firstSpanStart = prevElem.getStartOffset(); } else if( prevElem.getEndOffset() > firstPStart ) { newElements.addElement(new BidiElement(bidiRoot, prevElem.getStartOffset(), firstPStart, prevLevel)); } else { removeFromIndex++; } } int firstSpanEnd = 0; while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0])) firstSpanEnd++; // Calculate the last span of characters in the affected range with // the same bidi level. If this level is the same as the level of the // next bidi element (the existing bidi element containing lastPEnd), // then merge in the next element. If not, but the next element // overlaps the affected range, adjust the next element to start at // lastPEnd. int lastSpanEnd = lastPEnd; Element newNextElem = null; int removeToIndex = bidiRoot.getElementCount() - 1; if( lastSpanEnd <= getLength() ) { int nextElemIndex = bidiRoot.getElementIndex( lastPEnd ); removeToIndex = nextElemIndex; Element nextElem = bidiRoot.getElement( nextElemIndex ); int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes()); if( nextLevel == levels[levels.length-1] ) { lastSpanEnd = nextElem.getEndOffset(); } else if( nextElem.getStartOffset() < lastPEnd ) { newNextElem = new BidiElement(bidiRoot, lastPEnd, nextElem.getEndOffset(), nextLevel); } else { removeToIndex--; } } int lastSpanStart = levels.length; while( (lastSpanStart>firstSpanEnd) && (levels[lastSpanStart-1]==levels[levels.length-1]) ) lastSpanStart--; // If the first and last spans are contiguous and have the same level, // merge them and create a single new element for the entire span. // Otherwise, create elements for the first and last spans as well as // any spans in between. if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){ newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, lastSpanEnd, levels[0])); } else { // Create an element for the first span. newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, firstSpanEnd+firstPStart, levels[0])); // Create elements for the spans in between the first and last for( int i=firstSpanEnd; i<lastSpanStart; ) { //System.out.println("executed line 872"); int j; for( j=i; (j<levels.length) && (levels[j] == levels[i]); j++ ); newElements.addElement(new BidiElement(bidiRoot, firstPStart+i, firstPStart+j, (int)levels[i])); i=j; } // Create an element for the last span. newElements.addElement(new BidiElement(bidiRoot, lastSpanStart+firstPStart, lastSpanEnd, levels[levels.length-1])); } if( newNextElem != null ) newElements.addElement( newNextElem ); // Calculate the set of existing bidi elements which must be // removed. int removedElemCount = 0; if( bidiRoot.getElementCount() > 0 ) { removedElemCount = removeToIndex - removeFromIndex + 1; } Element[] removedElems = new Element[removedElemCount]; for( int i=0; i<removedElemCount; i++ ) { removedElems[i] = bidiRoot.getElement(removeFromIndex+i); } Element[] addedElems = new Element[ newElements.size() ]; newElements.copyInto( addedElems ); // Update the change record. ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex, removedElems, addedElems ); chng.addEdit( ee ); // Update the bidi element structure. bidiRoot.replace( removeFromIndex, removedElems.length, addedElems ); } /** * Calculate the levels array for a range of paragraphs. */ private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) { byte levels[] = new byte[ lastPEnd - firstPStart ]; int levelsEnd = 0; Boolean defaultDirection = null; Object d = getProperty(TextAttribute.RUN_DIRECTION); if (d instanceof Boolean) { defaultDirection = (Boolean) d; } // For each paragraph in the given range of paragraphs, get its // levels array and add it to the levels array for the entire span. for(int o=firstPStart; o<lastPEnd; ) { Element p = getParagraphElement( o ); int pStart = p.getStartOffset(); int pEnd = p.getEndOffset(); // default run direction for the paragraph. This will be // null if there is no direction override specified (i.e. // the direction will be determined from the content). Boolean direction = defaultDirection; d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); if (d instanceof Boolean) { direction = (Boolean) d; } //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd); // Create a Bidi over this paragraph then get the level // array. Segment seg = SegmentCache.getSharedSegment(); try { getText(pStart, pEnd-pStart, seg); } catch (BadLocationException e ) { throw new Error("Internal error: " + e.toString()); } // REMIND(bcb) we should really be using a Segment here. Bidi bidiAnalyzer; int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; if (direction != null) { if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) { bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT; } else { bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT; } } bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count, bidiflag); BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd); levelsEnd += bidiAnalyzer.getLength(); o = p.getEndOffset(); SegmentCache.releaseSharedSegment(seg); } // REMIND(bcb) remove this code when debugging is done. if( levelsEnd != levels.length ) throw new Error("levelsEnd assertion failed."); return levels; } /** * Gives a diagnostic dump. * * @param out the output stream */ public void dump(PrintStream out) { Element root = getDefaultRootElement(); if (root instanceof AbstractElement) { ((AbstractElement)root).dump(out, 0); } bidiRoot.dump(out,0); } /** * Gets the content for the document. * * @return the content */ protected final Content getContent() { return data; } /** * Creates a document leaf element. * Hook through which elements are created to represent the * document structure. Because this implementation keeps * structure and content separate, elements grow automatically * when content is extended so splits of existing elements * follow. The document itself gets to decide how to generate * elements to give flexibility in the type of elements used. * * @param parent the parent element * @param a the attributes for the element * @param p0 the beginning of the range >= 0 * @param p1 the end of the range >= p0 * @return the new element */ protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { return new LeafElement(parent, a, p0, p1); } /** * Creates a document branch element, that can contain other elements. * * @param parent the parent element * @param a the attributes * @return the element */ protected Element createBranchElement(Element parent, AttributeSet a) { return new BranchElement(parent, a); } // --- Document locking ---------------------------------- /** * Fetches the current writing thread if there is one. * This can be used to distinguish whether a method is * being called as part of an existing modification or * if a lock needs to be acquired and a new transaction * started. * * @return the thread actively modifying the document * or <code>null</code> if there are no modifications in progress */ protected synchronized final Thread getCurrentWriter() { return currWriter; } /** * Acquires a lock to begin mutating the document this lock * protects. There can be no writing, notification of changes, or * reading going on in order to gain the lock. Additionally a thread is * allowed to gain more than one <code>writeLock</code>, * as long as it doesn't attempt to gain additional <code>writeLock</code>s * from within document notification. Attempting to gain a * <code>writeLock</code> from within a DocumentListener notification will * result in an <code>IllegalStateException</code>. The ability * to obtain more than one <code>writeLock</code> per thread allows * subclasses to gain a writeLock, perform a number of operations, then * release the lock. * <p> * Calls to <code>writeLock</code> * must be balanced with calls to <code>writeUnlock</code>, else the * <code>Document</code> will be left in a locked state so that no * reading or writing can be done. * * @exception IllegalStateException thrown on illegal lock * attempt. If the document is implemented properly, this can * only happen if a document listener attempts to mutate the * document. This situation violates the bean event model * where order of delivery is not guaranteed and all listeners * should be notified before further mutations are allowed. */ protected synchronized final void writeLock() { try { while ((numReaders > 0) || (currWriter != null)) { if (Thread.currentThread() == currWriter) { if (notifyingListeners) { // Assuming one doesn't do something wrong in a // subclass this should only happen if a // DocumentListener tries to mutate the document. throw new IllegalStateException( "Attempt to mutate in notification"); } numWriters++; return; } wait(); } currWriter = Thread.currentThread(); numWriters = 1; } catch (InterruptedException e) { throw new Error("Interrupted attempt to aquire write lock"); } } /** * Releases a write lock previously obtained via <code>writeLock</code>. * After decrementing the lock count if there are no oustanding locks * this will allow a new writer, or readers. * * @see #writeLock */ protected synchronized final void writeUnlock() { if (--numWriters <= 0) { numWriters = 0; currWriter = null; notifyAll(); } } /** * Acquires a lock to begin reading some state from the * document. There can be multiple readers at the same time. * Writing blocks the readers until notification of the change * to the listeners has been completed. This method should * be used very carefully to avoid unintended compromise * of the document. It should always be balanced with a * <code>readUnlock</code>. * * @see #readUnlock */ public synchronized final void readLock() { try { while (currWriter != null) { if (currWriter == Thread.currentThread()) { // writer has full read access.... may try to acquire // lock in notification return; } wait(); } numReaders += 1; } catch (InterruptedException e) { throw new Error("Interrupted attempt to aquire read lock"); } } /** * Does a read unlock. This signals that one * of the readers is done. If there are no more readers * then writing can begin again. This should be balanced * with a readLock, and should occur in a finally statement * so that the balance is guaranteed. The following is an * example. * <pre><code> * readLock(); * try { * // do something * } finally { * readUnlock(); * } * </code></pre> * * @see #readLock */ public synchronized final void readUnlock() { if (currWriter == Thread.currentThread()) { // writer has full read access.... may try to acquire // lock in notification return; } if (numReaders <= 0) { throw new StateInvariantError(BAD_LOCK_STATE); } numReaders -= 1; notify(); } // --- serialization --------------------------------------------- private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); listenerList = new EventListenerList(); // Restore bidi structure //REMIND(bcb) This creates an initial bidi element to account for //the \n that exists by default in the content. bidiRoot = new BidiRootElement(); try { writeLock(); Element[] p = new Element[1]; p[0] = new BidiElement( bidiRoot, 0, 1, 0 ); bidiRoot.replace(0,0,p); } finally { writeUnlock(); } // At this point bidi root is only partially correct. To fully // restore it we need access to getDefaultRootElement. But, this // is created by the subclass and at this point will be null. We // thus use registerValidation. s.registerValidation(new ObjectInputValidation() { public void validateObject() { try { writeLock(); DefaultDocumentEvent e = new DefaultDocumentEvent (0, getLength(), DocumentEvent.EventType.INSERT); updateBidi( e ); } finally { writeUnlock(); } } }, 0); } // ----- member variables ------------------------------------------ private transient int numReaders; private transient Thread currWriter; /** * The number of writers, all obtained from <code>currWriter</code>. */ private transient int numWriters; /** * True will notifying listeners. */ private transient boolean notifyingListeners; private static Boolean defaultI18NProperty; /** * Storage for document-wide properties. */ private Dictionary<Object,Object> documentProperties = null; /** * The event listener list for the document. */ protected EventListenerList listenerList = new EventListenerList(); /** * Where the text is actually stored, and a set of marks * that track change as the document is edited are managed. */ private Content data; /** * Factory for the attributes. This is the strategy for * attribute compression and control of the lifetime of * a set of attributes as a collection. This may be sharedLines 1001–1500 of 3118. Previous lines Load more
* Updates any document structure as a result of text removal. This * method is called after the text has been removed from the Content. * This will happen within a write lock. If a subclass * of this class reimplements this method, it should delegate to the * superclass as well. * * @param chng a description of the change */ protected void postRemoveUpdate(DefaultDocumentEvent chng) { if( getProperty(I18NProperty).equals( Boolean.TRUE ) ) updateBidi( chng ); } /** * Update the bidi element structure as a result of the given change * to the document. The given change will be updated to reflect the * changes made to the bidi structure. * * This method assumes that every offset in the model is contained in * exactly one paragraph. This method also assumes that it is called * after the change is made to the default element structure. */ void updateBidi( DefaultDocumentEvent chng ) { // Calculate the range of paragraphs affected by the change. int firstPStart; int lastPEnd; if( chng.type == DocumentEvent.EventType.INSERT || chng.type == DocumentEvent.EventType.CHANGE ) { int chngStart = chng.getOffset(); int chngEnd = chngStart + chng.getLength(); firstPStart = getParagraphElement(chngStart).getStartOffset(); lastPEnd = getParagraphElement(chngEnd).getEndOffset(); } else if( chng.type == DocumentEvent.EventType.REMOVE ) { Element paragraph = getParagraphElement( chng.getOffset() ); firstPStart = paragraph.getStartOffset(); lastPEnd = paragraph.getEndOffset(); } else { throw new Error("Internal error: unknown event type."); } //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd ); // Calculate the bidi levels for the affected range of paragraphs. The // levels array will contain a bidi level for each character in the // affected text. byte levels[] = calculateBidiLevels( firstPStart, lastPEnd ); Vector<Element> newElements = new Vector<Element>(); // Calculate the first span of characters in the affected range with // the same bidi level. If this level is the same as the level of the // previous bidi element (the existing bidi element containing // firstPStart-1), then merge in the previous element. If not, but // the previous element overlaps the affected range, truncate the // previous element at firstPStart. int firstSpanStart = firstPStart; int removeFromIndex = 0; if( firstSpanStart > 0 ) { int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1); removeFromIndex = prevElemIndex; Element prevElem = bidiRoot.getElement(prevElemIndex); int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes()); //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]); if( prevLevel==levels[0] ) { firstSpanStart = prevElem.getStartOffset(); } else if( prevElem.getEndOffset() > firstPStart ) { newElements.addElement(new BidiElement(bidiRoot, prevElem.getStartOffset(), firstPStart, prevLevel)); } else { removeFromIndex++; } } int firstSpanEnd = 0; while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0])) firstSpanEnd++; // Calculate the last span of characters in the affected range with // the same bidi level. If this level is the same as the level of the // next bidi element (the existing bidi element containing lastPEnd), // then merge in the next element. If not, but the next element // overlaps the affected range, adjust the next element to start at // lastPEnd. int lastSpanEnd = lastPEnd; Element newNextElem = null; int removeToIndex = bidiRoot.getElementCount() - 1; if( lastSpanEnd <= getLength() ) { int nextElemIndex = bidiRoot.getElementIndex( lastPEnd ); removeToIndex = nextElemIndex; Element nextElem = bidiRoot.getElement( nextElemIndex ); int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes()); if( nextLevel == levels[levels.length-1] ) { lastSpanEnd = nextElem.getEndOffset(); } else if( nextElem.getStartOffset() < lastPEnd ) { newNextElem = new BidiElement(bidiRoot, lastPEnd, nextElem.getEndOffset(), nextLevel); } else { removeToIndex--; } } int lastSpanStart = levels.length; while( (lastSpanStart>firstSpanEnd) && (levels[lastSpanStart-1]==levels[levels.length-1]) ) lastSpanStart--; // If the first and last spans are contiguous and have the same level, // merge them and create a single new element for the entire span. // Otherwise, create elements for the first and last spans as well as // any spans in between. if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){ newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, lastSpanEnd, levels[0])); } else { // Create an element for the first span. newElements.addElement(new BidiElement(bidiRoot, firstSpanStart, firstSpanEnd+firstPStart, levels[0])); // Create elements for the spans in between the first and last for( int i=firstSpanEnd; i<lastSpanStart; ) { //System.out.println("executed line 872"); int j; for( j=i; (j<levels.length) && (levels[j] == levels[i]); j++ ); newElements.addElement(new BidiElement(bidiRoot, firstPStart+i, firstPStart+j, (int)levels[i])); i=j; } // Create an element for the last span. newElements.addElement(new BidiElement(bidiRoot, lastSpanStart+firstPStart, lastSpanEnd, levels[levels.length-1])); } if( newNextElem != null ) newElements.addElement( newNextElem ); // Calculate the set of existing bidi elements which must be // removed. int removedElemCount = 0; if( bidiRoot.getElementCount() > 0 ) { removedElemCount = removeToIndex - removeFromIndex + 1; } Element[] removedElems = new Element[removedElemCount]; for( int i=0; i<removedElemCount; i++ ) { removedElems[i] = bidiRoot.getElement(removeFromIndex+i); } Element[] addedElems = new Element[ newElements.size() ]; newElements.copyInto( addedElems ); // Update the change record. ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex, removedElems, addedElems ); chng.addEdit( ee ); // Update the bidi element structure. bidiRoot.replace( removeFromIndex, removedElems.length, addedElems ); } /** * Calculate the levels array for a range of paragraphs. */ private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) { byte levels[] = new byte[ lastPEnd - firstPStart ]; int levelsEnd = 0; Boolean defaultDirection = null; Object d = getProperty(TextAttribute.RUN_DIRECTION); if (d instanceof Boolean) { defaultDirection = (Boolean) d; } // For each paragraph in the given range of paragraphs, get its // levels array and add it to the levels array for the entire span. for(int o=firstPStart; o<lastPEnd; ) { Element p = getParagraphElement( o ); int pStart = p.getStartOffset(); int pEnd = p.getEndOffset(); // default run direction for the paragraph. This will be // null if there is no direction override specified (i.e. // the direction will be determined from the content). Boolean direction = defaultDirection; d = p.getAttributes().getAttribute(TextAttribute.RUN_DIRECTION); if (d instanceof Boolean) { direction = (Boolean) d; } //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd); // Create a Bidi over this paragraph then get the level // array. Segment seg = SegmentCache.getSharedSegment(); try { getText(pStart, pEnd-pStart, seg); } catch (BadLocationException e ) { throw new Error("Internal error: " + e.toString()); } // REMIND(bcb) we should really be using a Segment here. Bidi bidiAnalyzer; int bidiflag = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; if (direction != null) { if (TextAttribute.RUN_DIRECTION_LTR.equals(direction)) { bidiflag = Bidi.DIRECTION_LEFT_TO_RIGHT; } else { bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT; } } bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count, bidiflag); BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd); levelsEnd += bidiAnalyzer.getLength(); o = p.getEndOffset(); SegmentCache.releaseSharedSegment(seg); } // REMIND(bcb) remove this code when debugging is done. if( levelsEnd != levels.length ) throw new Error("levelsEnd assertion failed."); return levels; } /** * Gives a diagnostic dump. * * @param out the output stream */ public void dump(PrintStream out) { Element root = getDefaultRootElement(); if (root instanceof AbstractElement) { ((AbstractElement)root).dump(out, 0); } bidiRoot.dump(out,0); } /** * Gets the content for the document. * * @return the content */ protected final Content getContent() { return data; } /** * Creates a document leaf element. * Hook through which elements are created to represent the * document structure. Because this implementation keeps * structure and content separate, elements grow automatically * when content is extended so splits of existing elements * follow. The document itself gets to decide how to generate * elements to give flexibility in the type of elements used. * * @param parent the parent element * @param a the attributes for the element * @param p0 the beginning of the range >= 0 * @param p1 the end of the range >= p0 * @return the new element */ protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) { return new LeafElement(parent, a, p0, p1); } /** * Creates a document branch element, that can contain other elements. * * @param parent the parent element * @param a the attributes * @return the element */ protected Element createBranchElement(Element parent, AttributeSet a) { return new BranchElement(parent, a); } // --- Document locking ---------------------------------- /** * Fetches the current writing thread if there is one. * This can be used to distinguish whether a method is * being called as part of an existing modification or * if a lock needs to be acquired and a new transaction * started. * * @return the thread actively modifying the document * or <code>null</code> if there are no modifications in progress */ protected synchronized final Thread getCurrentWriter() { return currWriter; } /** * Acquires a lock to begin mutating the document this lock * protects. There can be no writing, notification of changes, or * reading going on in order to gain the lock. Additionally a thread is * allowed to gain more than one <code>writeLock</code>, * as long as it doesn't attempt to gain additional <code>writeLock</code>s * from within document notification. Attempting to gain a * <code>writeLock</code> from within a DocumentListener notification will * result in an <code>IllegalStateException</code>. The ability * to obtain more than one <code>writeLock</code> per thread allows * subclasses to gain a writeLock, perform a number of operations, then * release the lock. * <p> * Calls to <code>writeLock</code> * must be balanced with calls to <code>writeUnlock</code>, else the * <code>Document</code> will be left in a locked state so that no * reading or writing can be done. * * @exception IllegalStateException thrown on illegal lock * attempt. If the document is implemented properly, this can * only happen if a document listener attempts to mutate the * document. This situation violates the bean event model * where order of delivery is not guaranteed and all listeners * should be notified before further mutations are allowed. */ protected synchronized final void writeLock() { try { while ((numReaders > 0) || (currWriter != null)) { if (Thread.currentThread() == currWriter) { if (notifyingListeners) { // Assuming one doesn't do something wrong in a // subclass this should only happen if a // DocumentListener tries to mutate the document. throw new IllegalStateException( "Attempt to mutate in notification"); } numWriters++; return; } wait(); } currWriter = Thread.currentThread(); numWriters = 1; } catch (InterruptedException e) { throw new Error("Interrupted attempt to aquire write lock"); } } /** * Releases a write lock previously obtained via <code>writeLock</code>. * After decrementing the lock count if there are no oustanding locks * this will allow a new writer, or readers. * * @see #writeLock */ protected synchronized final void writeUnlock() { if (--numWriters <= 0) { numWriters = 0; currWriter = null; notifyAll(); } } /** * Acquires a lock to begin reading some state from the * document. There can be multiple readers at the same time. * Writing blocks the readers until notification of the change * to the listeners has been completed. This method should * be used very carefully to avoid unintended compromise * of the document. It should always be balanced with a * <code>readUnlock</code>. * * @see #readUnlock */ public synchronized final void readLock() { try { while (currWriter != null) { if (currWriter == Thread.currentThread()) { // writer has full read access.... may try to acquire // lock in notification return; } wait(); } numReaders += 1; } catch (InterruptedException e) { throw new Error("Interrupted attempt to aquire read lock"); } } /** * Does a read unlock. This signals that one * of the readers is done. If there are no more readers * then writing can begin again. This should be balanced * with a readLock, and should occur in a finally statement * so that the balance is guaranteed. The following is an * example. * <pre><code> * readLock(); * try { * // do something * } finally { * readUnlock(); * } * </code></pre> * * @see #readLock */ public synchronized final void readUnlock() { if (currWriter == Thread.currentThread()) { // writer has full read access.... may try to acquire // lock in notification return; } if (numReaders <= 0) { throw new StateInvariantError(BAD_LOCK_STATE); } numReaders -= 1; notify(); } // --- serialization --------------------------------------------- private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); listenerList = new EventListenerList(); // Restore bidi structure //REMIND(bcb) This creates an initial bidi element to account for //the \n that exists by default in the content. bidiRoot = new BidiRootElement(); try { writeLock(); Element[] p = new Element[1]; p[0] = new BidiElement( bidiRoot, 0, 1, 0 ); bidiRoot.replace(0,0,p); } finally { writeUnlock(); } // At this point bidi root is only partially correct. To fully // restore it we need access to getDefaultRootElement. But, this // is created by the subclass and at this point will be null. We // thus use registerValidation. s.registerValidation(new ObjectInputValidation() { public void validateObject() { try { writeLock(); DefaultDocumentEvent e = new DefaultDocumentEvent (0, getLength(), DocumentEvent.EventType.INSERT); updateBidi( e ); } finally { writeUnlock(); } } }, 0); } // ----- member variables ------------------------------------------ private transient int numReaders; private transient Thread currWriter; /** * The number of writers, all obtained from <code>currWriter</code>. */ private transient int numWriters; /** * True will notifying listeners. */ private transient boolean notifyingListeners; private static Boolean defaultI18NProperty; /** * Storage for document-wide properties. */ private Dictionary<Object,Object> documentProperties = null; /** * The event listener list for the document. */ protected EventListenerList listenerList = new EventListenerList(); /** * Where the text is actually stored, and a set of marks * that track change as the document is edited are managed. */ private Content data; /** * Factory for the attributes. This is the strategy for * attribute compression and control of the lifetime of * a set of attributes as a collection. This may be shared