127 |
* Manages event listeners for this <code>Document</code>. |
* Manages event listeners for this <code>Document</code>. |
128 |
*/ |
*/ |
129 |
protected EventListenerList listenerList = new EventListenerList(); |
protected EventListenerList listenerList = new EventListenerList(); |
130 |
|
|
131 |
|
/** |
132 |
|
* Stores the current writer thread. Used for locking. |
133 |
|
*/ |
134 |
|
private Thread currentWriter = null; |
135 |
|
|
136 |
|
/** |
137 |
|
* The number of readers. Used for locking. |
138 |
|
*/ |
139 |
|
private int numReaders = 0; |
140 |
|
|
141 |
|
/** |
142 |
|
* Tells if there are one or more writers waiting. |
143 |
|
*/ |
144 |
|
private int numWritersWaiting = 0; |
145 |
|
|
146 |
/** |
/** |
147 |
|
* A condition variable that readers and writers wait on. |
148 |
|
*/ |
149 |
|
Object documentCV = new Object(); |
150 |
|
|
151 |
|
|
152 |
|
/** |
153 |
* Creates a new <code>AbstractDocument</code> with the specified |
* Creates a new <code>AbstractDocument</code> with the specified |
154 |
* {@link Content} model. |
* {@link Content} model. |
155 |
* |
* |
368 |
*/ |
*/ |
369 |
protected Thread getCurrentWriter() |
protected Thread getCurrentWriter() |
370 |
{ |
{ |
371 |
// FIXME: Implement locking! |
return currentWriter; |
|
return null; |
|
372 |
} |
} |
373 |
|
|
374 |
/** |
/** |
535 |
// Just return when no text to insert was given. |
// Just return when no text to insert was given. |
536 |
if (text == null || text.length() == 0) |
if (text == null || text.length() == 0) |
537 |
return; |
return; |
|
|
|
538 |
DefaultDocumentEvent event = |
DefaultDocumentEvent event = |
539 |
new DefaultDocumentEvent(offset, text.length(), |
new DefaultDocumentEvent(offset, text.length(), |
540 |
DocumentEvent.EventType.INSERT); |
DocumentEvent.EventType.INSERT); |
541 |
content.insertString(offset, text); |
|
542 |
|
writeLock(); |
543 |
|
UndoableEdit undo = content.insertString(offset, text); |
544 |
insertUpdate(event, attributes); |
insertUpdate(event, attributes); |
545 |
|
writeUnlock(); |
546 |
|
|
547 |
fireInsertUpdate(event); |
fireInsertUpdate(event); |
548 |
|
if (undo != null) |
549 |
|
fireUndoableEditUpdate(new UndoableEditEvent(this, undo)); |
550 |
} |
} |
551 |
|
|
552 |
/** |
/** |
590 |
} |
} |
591 |
|
|
592 |
/** |
/** |
593 |
* Blocks until a read lock can be obtained. |
* Blocks until a read lock can be obtained. Must block if there is |
594 |
|
* currently a writer modifying the <code>Document</code>. |
595 |
*/ |
*/ |
596 |
public void readLock() |
public void readLock() |
597 |
{ |
{ |
598 |
|
if (currentWriter != null && currentWriter.equals(Thread.currentThread())) |
599 |
|
return; |
600 |
|
synchronized (documentCV) |
601 |
|
{ |
602 |
|
while (currentWriter != null || numWritersWaiting > 0) |
603 |
|
{ |
604 |
|
try |
605 |
|
{ |
606 |
|
documentCV.wait(); |
607 |
|
} |
608 |
|
catch (InterruptedException ie) |
609 |
|
{ |
610 |
|
throw new Error("interrupted trying to get a readLock"); |
611 |
|
} |
612 |
|
} |
613 |
|
numReaders++; |
614 |
|
} |
615 |
} |
} |
616 |
|
|
617 |
/** |
/** |
620 |
*/ |
*/ |
621 |
public void readUnlock() |
public void readUnlock() |
622 |
{ |
{ |
623 |
|
// Note we could have a problem here if readUnlock was called without a |
624 |
|
// prior call to readLock but the specs simply warn users to ensure that |
625 |
|
// balance by using a finally block: |
626 |
|
// readLock() |
627 |
|
// try |
628 |
|
// { |
629 |
|
// doSomethingHere |
630 |
|
// } |
631 |
|
// finally |
632 |
|
// { |
633 |
|
// readUnlock(); |
634 |
|
// } |
635 |
|
|
636 |
|
// All that the JDK seems to check for is that you don't call unlock |
637 |
|
// more times than you've previously called lock, but it doesn't make |
638 |
|
// sure that the threads calling unlock were the same ones that called lock |
639 |
|
|
640 |
|
// FIXME: the reference implementation throws a |
641 |
|
// javax.swing.text.StateInvariantError here |
642 |
|
if (numReaders == 0) |
643 |
|
throw new IllegalStateException("document lock failure"); |
644 |
|
|
645 |
|
synchronized (documentCV) |
646 |
|
{ |
647 |
|
// If currentWriter is not null, the application code probably had a |
648 |
|
// writeLock and then tried to obtain a readLock, in which case |
649 |
|
// numReaders wasn't incremented |
650 |
|
if (currentWriter == null) |
651 |
|
{ |
652 |
|
numReaders --; |
653 |
|
if (numReaders == 0 && numWritersWaiting != 0) |
654 |
|
documentCV.notify(); |
655 |
|
} |
656 |
|
} |
657 |
} |
} |
658 |
|
|
659 |
/** |
/** |
671 |
DefaultDocumentEvent event = |
DefaultDocumentEvent event = |
672 |
new DefaultDocumentEvent(offset, length, |
new DefaultDocumentEvent(offset, length, |
673 |
DocumentEvent.EventType.REMOVE); |
DocumentEvent.EventType.REMOVE); |
674 |
|
|
675 |
|
// Here we set up the parameters for an ElementChange, if one |
676 |
|
// needs to be added to the DocumentEvent later |
677 |
|
Element root = getDefaultRootElement(); |
678 |
|
int start = root.getElementIndex(offset); |
679 |
|
int end = root.getElementIndex(offset + length); |
680 |
|
|
681 |
|
Element[] removed = new Element[end - start + 1]; |
682 |
|
for (int i = start; i <= end; i++) |
683 |
|
removed[i - start] = root.getElement(i); |
684 |
|
|
685 |
removeUpdate(event); |
removeUpdate(event); |
686 |
content.remove(offset, length); |
|
687 |
|
Element[] added = new Element[1]; |
688 |
|
added[0] = root.getElement(start); |
689 |
|
boolean shouldFire = content.getString(offset, length).length() != 0; |
690 |
|
|
691 |
|
writeLock(); |
692 |
|
UndoableEdit temp = content.remove(offset, length); |
693 |
|
writeUnlock(); |
694 |
|
|
695 |
postRemoveUpdate(event); |
postRemoveUpdate(event); |
696 |
fireRemoveUpdate(event); |
|
697 |
|
GapContent.UndoRemove changes = null; |
698 |
|
if (content instanceof GapContent) |
699 |
|
changes = (GapContent.UndoRemove) temp; |
700 |
|
|
701 |
|
if (changes != null && !(start == end)) |
702 |
|
{ |
703 |
|
// We need to add an ElementChange to our DocumentEvent |
704 |
|
ElementEdit edit = new ElementEdit (root, start, removed, added); |
705 |
|
event.addEdit(edit); |
706 |
|
} |
707 |
|
|
708 |
|
if (shouldFire) |
709 |
|
fireRemoveUpdate(event); |
710 |
} |
} |
711 |
|
|
712 |
/** |
/** |
821 |
*/ |
*/ |
822 |
public void render(Runnable runnable) |
public void render(Runnable runnable) |
823 |
{ |
{ |
824 |
// FIXME: Implement me! |
readLock(); |
825 |
|
try |
826 |
|
{ |
827 |
|
runnable.run(); |
828 |
|
} |
829 |
|
finally |
830 |
|
{ |
831 |
|
readUnlock(); |
832 |
|
} |
833 |
} |
} |
834 |
|
|
835 |
/** |
/** |
841 |
*/ |
*/ |
842 |
public void setAsynchronousLoadPriority(int p) |
public void setAsynchronousLoadPriority(int p) |
843 |
{ |
{ |
844 |
|
// TODO: Implement this properly. |
845 |
} |
} |
846 |
|
|
847 |
/** |
/** |
856 |
} |
} |
857 |
|
|
858 |
/** |
/** |
859 |
* Blocks until a write lock can be obtained. |
* Blocks until a write lock can be obtained. Must wait if there are |
860 |
|
* readers currently reading or another thread is currently writing. |
861 |
*/ |
*/ |
862 |
protected void writeLock() |
protected void writeLock() |
863 |
{ |
{ |
864 |
// FIXME: Implement me. |
if (currentWriter!= null && currentWriter.equals(Thread.currentThread())) |
865 |
|
return; |
866 |
|
synchronized (documentCV) |
867 |
|
{ |
868 |
|
numWritersWaiting++; |
869 |
|
while (numReaders > 0) |
870 |
|
{ |
871 |
|
try |
872 |
|
{ |
873 |
|
documentCV.wait(); |
874 |
|
} |
875 |
|
catch (InterruptedException ie) |
876 |
|
{ |
877 |
|
throw new Error("interruped while trying to obtain write lock"); |
878 |
|
} |
879 |
|
} |
880 |
|
numWritersWaiting --; |
881 |
|
currentWriter = Thread.currentThread(); |
882 |
|
} |
883 |
} |
} |
884 |
|
|
885 |
/** |
/** |
888 |
*/ |
*/ |
889 |
protected void writeUnlock() |
protected void writeUnlock() |
890 |
{ |
{ |
891 |
// FIXME: Implement me. |
synchronized (documentCV) |
892 |
|
{ |
893 |
|
if (Thread.currentThread().equals(currentWriter)) |
894 |
|
{ |
895 |
|
currentWriter = null; |
896 |
|
documentCV.notifyAll(); |
897 |
|
} |
898 |
|
} |
899 |
} |
} |
900 |
|
|
901 |
/** |
/** |
1374 |
|
|
1375 |
/** |
/** |
1376 |
* Returns the resolve parent of this element. |
* Returns the resolve parent of this element. |
1377 |
|
* This is taken from the AttributeSet, but if this is null, |
1378 |
|
* this method instead returns the Element's parent's |
1379 |
|
* AttributeSet |
1380 |
* |
* |
1381 |
* @return the resolve parent of this element |
* @return the resolve parent of this element |
1382 |
* |
* |
1384 |
*/ |
*/ |
1385 |
public AttributeSet getResolveParent() |
public AttributeSet getResolveParent() |
1386 |
{ |
{ |
1387 |
return attributes.getResolveParent(); |
if (attributes.getResolveParent() != null) |
1388 |
|
return attributes.getResolveParent(); |
1389 |
|
return element_parent.getAttributes(); |
1390 |
} |
} |
1391 |
|
|
1392 |
/** |
/** |
1556 |
+ "must not be thrown " |
+ "must not be thrown " |
1557 |
+ "here."); |
+ "here."); |
1558 |
err.initCause(ex); |
err.initCause(ex); |
1559 |
|
throw err; |
1560 |
} |
} |
1561 |
b.append("]\n"); |
b.append("]\n"); |
1562 |
} |
} |
1664 |
*/ |
*/ |
1665 |
public int getElementIndex(int offset) |
public int getElementIndex(int offset) |
1666 |
{ |
{ |
1667 |
// If we have no children, return -1. |
// If offset is less than the start offset of our first child, |
1668 |
if (getElementCount() == 0) |
// return 0 |
1669 |
return - 1; |
if (offset < getStartOffset()) |
1670 |
|
return 0; |
1671 |
|
|
1672 |
// XXX: There is surely a better algorithm |
// XXX: There is surely a better algorithm |
1673 |
// as beginning from first element each time. |
// as beginning from first element each time. |
1674 |
for (int index = 0; index < children.length; ++index) |
for (int index = 0; index < children.length - 1; ++index) |
1675 |
{ |
{ |
1676 |
Element elem = children[index]; |
Element elem = children[index]; |
1677 |
|
|
1678 |
if ((elem.getStartOffset() <= offset) |
if ((elem.getStartOffset() <= offset) |
1679 |
&& (offset < elem.getEndOffset())) |
&& (offset < elem.getEndOffset())) |
1680 |
return index; |
return index; |
1681 |
|
// If the next element's start offset is greater than offset |
1682 |
|
// then we have to return the closest Element, since no Elements |
1683 |
|
// will contain the offset |
1684 |
|
if (children[index + 1].getStartOffset() > offset) |
1685 |
|
{ |
1686 |
|
if ((offset - elem.getEndOffset()) > (children[index + 1].getStartOffset() - offset)) |
1687 |
|
return index + 1; |
1688 |
|
else |
1689 |
|
return index; |
1690 |
|
} |
1691 |
} |
} |
1692 |
|
|
1693 |
// If offset is greater than the index of the last element, return |
// If offset is greater than the index of the last element, return |
1920 |
return (DocumentEvent.ElementChange) changes.get(elem); |
return (DocumentEvent.ElementChange) changes.get(elem); |
1921 |
} |
} |
1922 |
} |
} |
1923 |
|
|
1924 |
/** |
/** |
1925 |
* An implementation of {@link DocumentEvent.ElementChange} to be added |
* An implementation of {@link DocumentEvent.ElementChange} to be added |
1926 |
* to {@link DefaultDocumentEvent}s. |
* to {@link DefaultDocumentEvent}s. |