1. /*
  2. * @(#)AbstractDocument.java 1.101 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text;
  8. import java.util.*;
  9. import java.io.*;
  10. import javax.swing.undo.*;
  11. import javax.swing.event.ChangeListener;
  12. import javax.swing.event.*;
  13. import javax.swing.tree.TreeNode;
  14. /**
  15. * An implementation of the document interface to serve as a
  16. * basis for implementing various kinds of documents. At this
  17. * level there is very little policy, so there is a corresponding
  18. * increase in difficulty of use.
  19. * <p>
  20. * This class implements a locking mechanism for the document. It
  21. * allows multiple readers or one writer, and writers must wait until
  22. * all observers of the document have been notified of a previous
  23. * change before beginning another mutation to the document. The
  24. * read lock is aquired and released using the <code>render</code>
  25. * method. A write lock is aquired by the methods that mutate the
  26. * document, and are held for the duration of the method call.
  27. * Notification is done on the thread that produced the mutation,
  28. * and the thread has full read access to the document for the
  29. * duration of the notification, but other readers are kept out
  30. * until the notification has finished. The notification is a
  31. * beans event notification which does not allow any further
  32. * mutations until all listeners have been notified.
  33. * <p>
  34. * Any models subclassed from this class and used in conjunction
  35. * with a text component that has a look and feel implementation
  36. * that is derived from BasicTextUI may be safely updated
  37. * asynchronously, because all access to the View hierarchy
  38. * is serialized by BasicTextUI if the document is of type
  39. * <code>AbstractDocument</code>. The locking assumes that an
  40. * independant thread will access the View hierarchy only from
  41. * the DocumentListener methods, and that there will be only
  42. * one event thread active at a time.
  43. * <p>
  44. * If concurrency support is desired, there are the following
  45. * additional implications. The code path for any DocumentListener
  46. * implementation and any UndoListener implementation must be threadsafe,
  47. * and not access the component lock if trying to be safe from deadlocks.
  48. * The <code>repaint</code> and <code>revalidate</code> methods
  49. * on JComponent are safe.
  50. * <p>
  51. * <strong>Warning:</strong>
  52. * Serialized objects of this class will not be compatible with
  53. * future Swing releases. The current serialization support is appropriate
  54. * for short term storage or RMI between applications running the same
  55. * version of Swing. A future release of Swing will provide support for
  56. * long term persistence.
  57. *
  58. * @author Timothy Prinzing
  59. * @version 1.101 11/29/01
  60. */
  61. public abstract class AbstractDocument implements Document, Serializable {
  62. /**
  63. * Constructs a new AbstractDocument, wrapped around some
  64. * specified content storage mechanism.
  65. *
  66. * @param data the content
  67. */
  68. protected AbstractDocument(Content data) {
  69. this(data, StyleContext.getDefaultStyleContext());
  70. }
  71. /**
  72. * Constructs a new AbstractDocument, wrapped around some
  73. * specified content storage mechanism.
  74. *
  75. * @param data the content
  76. * @param context the attribute context
  77. */
  78. protected AbstractDocument(Content data, AttributeContext context) {
  79. this.data = data;
  80. this.context = context;
  81. bidiRoot = new BidiRootElement();
  82. //if (Utilities.is1dot2) {
  83. //putProperty( I18NProperty, Boolean.TRUE );
  84. //} else {
  85. putProperty( I18NProperty, Boolean.FALSE );
  86. //}
  87. //REMIND(bcb) This creates an initial bidi element to account for
  88. //the \n that exists by default in the content. Doing it this way
  89. //seems to expose a little too much knowledge of the content given
  90. //to us by the sub-class. Consider having the sub-class' constructor
  91. //make an initial call to insertUpdate.
  92. try {
  93. writeLock();
  94. Element[] p = new Element[1];
  95. p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
  96. bidiRoot.replace(0,0,p);
  97. } finally {
  98. writeUnlock();
  99. }
  100. }
  101. /**
  102. * Support for managing a set of properties. Callers
  103. * can use the documentProperties dictionary to annotate the
  104. * document with document-wide properties.
  105. *
  106. * @return a non null Dictionary
  107. * @see #setDocumentProperties
  108. */
  109. public Dictionary getDocumentProperties() {
  110. if (documentProperties == null) {
  111. documentProperties = new Hashtable(2);
  112. }
  113. return documentProperties;
  114. }
  115. /**
  116. * Replace the document properties dictionary for this document.
  117. *
  118. * @param x the new dictionary
  119. * @see #getDocumentProperties
  120. */
  121. public void setDocumentProperties(Dictionary x) {
  122. documentProperties = x;
  123. }
  124. /**
  125. * Notifies all listeners that have registered interest for
  126. * notification on this event type. The event instance
  127. * is lazily created using the parameters passed into
  128. * the fire method.
  129. *
  130. * @param e the event
  131. * @see EventListenerList
  132. */
  133. protected void fireInsertUpdate(DocumentEvent e) {
  134. // Guaranteed to return a non-null array
  135. Object[] listeners = listenerList.getListenerList();
  136. // Process the listeners last to first, notifying
  137. // those that are interested in this event
  138. for (int i = listeners.length-2; i>=0; i-=2) {
  139. if (listeners[i]==DocumentListener.class) {
  140. // Lazily create the event:
  141. // if (e == null)
  142. // e = new ListSelectionEvent(this, firstIndex, lastIndex);
  143. ((DocumentListener)listeners[i+1]).insertUpdate(e);
  144. }
  145. }
  146. }
  147. /**
  148. * Notifies all listeners that have registered interest for
  149. * notification on this event type. The event instance
  150. * is lazily created using the parameters passed into
  151. * the fire method.
  152. *
  153. * @param e the event
  154. * @see EventListenerList
  155. */
  156. protected void fireChangedUpdate(DocumentEvent e) {
  157. // Guaranteed to return a non-null array
  158. Object[] listeners = listenerList.getListenerList();
  159. // Process the listeners last to first, notifying
  160. // those that are interested in this event
  161. for (int i = listeners.length-2; i>=0; i-=2) {
  162. if (listeners[i]==DocumentListener.class) {
  163. // Lazily create the event:
  164. // if (e == null)
  165. // e = new ListSelectionEvent(this, firstIndex, lastIndex);
  166. ((DocumentListener)listeners[i+1]).changedUpdate(e);
  167. }
  168. }
  169. }
  170. /**
  171. * Notifies all listeners that have registered interest for
  172. * notification on this event type. The event instance
  173. * is lazily created using the parameters passed into
  174. * the fire method.
  175. *
  176. * @param e the event
  177. * @see EventListenerList
  178. */
  179. protected void fireRemoveUpdate(DocumentEvent e) {
  180. // Guaranteed to return a non-null array
  181. Object[] listeners = listenerList.getListenerList();
  182. // Process the listeners last to first, notifying
  183. // those that are interested in this event
  184. for (int i = listeners.length-2; i>=0; i-=2) {
  185. if (listeners[i]==DocumentListener.class) {
  186. // Lazily create the event:
  187. // if (e == null)
  188. // e = new ListSelectionEvent(this, firstIndex, lastIndex);
  189. ((DocumentListener)listeners[i+1]).removeUpdate(e);
  190. }
  191. }
  192. }
  193. /**
  194. * Notifies all listeners that have registered interest for
  195. * notification on this event type. The event instance
  196. * is lazily created using the parameters passed into
  197. * the fire method.
  198. *
  199. * @param e the event
  200. * @see EventListenerList
  201. */
  202. protected void fireUndoableEditUpdate(UndoableEditEvent e) {
  203. // Guaranteed to return a non-null array
  204. Object[] listeners = listenerList.getListenerList();
  205. // Process the listeners last to first, notifying
  206. // those that are interested in this event
  207. for (int i = listeners.length-2; i>=0; i-=2) {
  208. if (listeners[i]==UndoableEditListener.class) {
  209. // Lazily create the event:
  210. // if (e == null)
  211. // e = new ListSelectionEvent(this, firstIndex, lastIndex);
  212. ((UndoableEditListener)listeners[i+1]).undoableEditHappened(e);
  213. }
  214. }
  215. }
  216. /**
  217. * Get the asynchronous loading priority. If less than zero,
  218. * the document should not be loaded asynchronously.
  219. */
  220. public int getAsynchronousLoadPriority() {
  221. Integer loadPriority = (Integer)
  222. getProperty(AbstractDocument.AsyncLoadPriority);
  223. if (loadPriority != null) {
  224. return loadPriority.intValue();
  225. }
  226. return -1;
  227. }
  228. /**
  229. * Set the asynchronous loading priority.
  230. */
  231. public void setAsynchronousLoadPriority(int p) {
  232. Integer loadPriority = (p >= 0) ? new Integer(p) : null;
  233. putProperty(AbstractDocument.AsyncLoadPriority, loadPriority);
  234. }
  235. // --- Document methods -----------------------------------------
  236. /**
  237. * This allows the model to be safely rendered in the presence
  238. * of currency, if the model supports being updated asynchronously.
  239. * The given runnable will be executed in a way that allows it
  240. * to safely read the model with no changes while the runnable
  241. * is being executed. The runnable itself may <em>not</em>
  242. * make any mutations.
  243. * <p>
  244. * This is implemented to aquire a read lock for the duration
  245. * of the runnables execution. There may be multiple runnables
  246. * executing at the same time, and all writers will be blocked
  247. * while there are active rendering runnables. If the runnable
  248. * throws an exception, its lock will be safely released.
  249. * There is no protection against a runnable that never exits,
  250. * which will effectively leave the document locked for it's
  251. * lifetime.
  252. * <p>
  253. * If the given runnable attempts to make any mutations in
  254. * this implementation, a deadlock will occur. There is
  255. * no tracking of individual rendering threads to enable
  256. * detecting this situation, but a subclass could incur
  257. * the overhead of tracking them and throwing an error.
  258. * <p>
  259. * This method is thread safe, although most Swing methods
  260. * are not. Please see
  261. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  262. * and Swing</A> for more information.
  263. *
  264. * @param r the renderer to execute.
  265. */
  266. public void render(Runnable r) {
  267. try {
  268. readLock();
  269. r.run();
  270. } finally {
  271. readUnlock();
  272. }
  273. }
  274. /**
  275. * Returns the length of the data. This is the number of
  276. * characters of content that represents the users data.
  277. *
  278. * @return the length >= 0
  279. * @see Document#getLength
  280. */
  281. public int getLength() {
  282. return data.length() - 1;
  283. }
  284. /**
  285. * Adds a document listener for notification of any changes.
  286. *
  287. * @param listener the listener
  288. * @see Document#addDocumentListener
  289. */
  290. public void addDocumentListener(DocumentListener listener) {
  291. listenerList.add(DocumentListener.class, listener);
  292. }
  293. /**
  294. * Removes a document listener.
  295. *
  296. * @param listener the listener
  297. * @see Document#removeDocumentListener
  298. */
  299. public void removeDocumentListener(DocumentListener listener) {
  300. listenerList.remove(DocumentListener.class, listener);
  301. }
  302. /**
  303. * Adds an undo listener for notification of any changes.
  304. * Undo/Redo operations performed on the UndoableEdit will
  305. * cause the appropriate DocumentEvent to be fired to keep
  306. * the view(s) in sync with the model.
  307. *
  308. * @param listener the listener
  309. * @see Document#addUndoableEditListener
  310. */
  311. public void addUndoableEditListener(UndoableEditListener listener) {
  312. listenerList.add(UndoableEditListener.class, listener);
  313. }
  314. /**
  315. * Removes an undo listener.
  316. *
  317. * @param listener the listener
  318. * @see Document#removeDocumentListener
  319. */
  320. public void removeUndoableEditListener(UndoableEditListener listener) {
  321. listenerList.remove(UndoableEditListener.class, listener);
  322. }
  323. /**
  324. * A convenience method for looking up a property value. It is
  325. * equivalent to:
  326. * <pre>
  327. * getDocumentProperties().get(key);
  328. * </pre>
  329. *
  330. * @param key the non-null property key
  331. * @return the value of this property or null
  332. * @see #getDocumentProperties
  333. */
  334. public final Object getProperty(Object key) {
  335. return getDocumentProperties().get(key);
  336. }
  337. /**
  338. * A convenience method for storing up a property value. It is
  339. * equivalent to:
  340. * <pre>
  341. * getDocumentProperties().put(key, value);
  342. * </pre>
  343. * If value is null this method will remove the property
  344. *
  345. * @param key the non-null key
  346. * @param value the value
  347. * @see #getDocumentProperties
  348. */
  349. public final void putProperty(Object key, Object value) {
  350. if (value != null) {
  351. getDocumentProperties().put(key, value);
  352. } else
  353. getDocumentProperties().remove(key);
  354. }
  355. /**
  356. * Removes some content from the document.
  357. * Removing content causes a write lock to be held while the
  358. * actual changes are taking place. Observers are notified
  359. * of the change on the thread that called this method.
  360. * <p>
  361. * This method is thread safe, although most Swing methods
  362. * are not. Please see
  363. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  364. * and Swing</A> for more information.
  365. *
  366. * @param offs the starting offset >= 0
  367. * @param len the number of characters to remove >= 0
  368. * @exception BadLocationException the given remove position is not a valid
  369. * position within the document
  370. * @see Document#remove
  371. */
  372. public void remove(int offs, int len) throws BadLocationException {
  373. if (len > 0) {
  374. try {
  375. writeLock();
  376. DefaultDocumentEvent chng =
  377. new DefaultDocumentEvent(offs, len, DocumentEvent.EventType.REMOVE);
  378. boolean isComposedTextElement = false;
  379. if (Utilities.is1dot2) {
  380. // Check whether the position of interest is the composed text
  381. Element elem = getDefaultRootElement();
  382. while (!elem.isLeaf()) {
  383. elem = elem.getElement(elem.getElementIndex(offs));
  384. }
  385. isComposedTextElement = Utilities.isComposedTextElement(elem);
  386. }
  387. removeUpdate(chng);
  388. UndoableEdit u = data.remove(offs, len);
  389. if (u != null) {
  390. chng.addEdit(u);
  391. }
  392. postRemoveUpdate(chng);
  393. // Mark the edit as done.
  394. chng.end();
  395. fireRemoveUpdate(chng);
  396. // only fire undo if Content implementation supports it
  397. // undo for the composed text is not supported for now
  398. if ((u != null) && !isComposedTextElement) {
  399. fireUndoableEditUpdate(new UndoableEditEvent(this, chng));
  400. }
  401. } finally {
  402. writeUnlock();
  403. }
  404. }
  405. }
  406. /**
  407. * Inserts some content into the document.
  408. * Inserting content causes a write lock to be held while the
  409. * actual changes are taking place, followed by notification
  410. * to the observers on the thread that grabbed the write lock.
  411. * <p>
  412. * This method is thread safe, although most Swing methods
  413. * are not. Please see
  414. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  415. * and Swing</A> for more information.
  416. *
  417. * @param offs the starting offset >= 0
  418. * @param str the string to insert; does nothing with null/empty strings
  419. * @param a the attributes for the inserted content
  420. * @exception BadLocationException the given insert position is not a valid
  421. * position within the document
  422. * @see Document#insertString
  423. */
  424. public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
  425. if ((str == null) || (str.length() == 0)) {
  426. return;
  427. }
  428. try {
  429. writeLock();
  430. UndoableEdit u = data.insertString(offs, str);
  431. DefaultDocumentEvent e =
  432. new DefaultDocumentEvent(offs, str.length(), DocumentEvent.EventType.INSERT);
  433. if (u != null) {
  434. e.addEdit(u);
  435. }
  436. insertUpdate(e, a);
  437. // Mark the edit as done.
  438. e.end();
  439. fireInsertUpdate(e);
  440. // only fire undo if Content implementation supports it
  441. // undo for the composed text is not supported for now
  442. if (u != null &&
  443. (a == null || !a.isDefined(StyleConstants.ComposedTextAttribute))) {
  444. fireUndoableEditUpdate(new UndoableEditEvent(this, e));
  445. }
  446. } finally {
  447. writeUnlock();
  448. }
  449. }
  450. /**
  451. * Gets a sequence of text from the document.
  452. *
  453. * @param offset the starting offset >= 0
  454. * @param length the number of characters to retrieve >= 0
  455. * @return the text
  456. * @exception BadLocationException the range given includes a position
  457. * that is not a valid position within the document
  458. * @see Document#getText
  459. */
  460. public String getText(int offset, int length) throws BadLocationException {
  461. String str = data.getString(offset, length);
  462. return str;
  463. }
  464. /**
  465. * Gets some text from the document potentially without
  466. * making a copy. The character array returned in the
  467. * given <code>Segment</code> should never be mutated.
  468. * This kind of access to the characters of the document
  469. * is provided to help make the rendering potentially more
  470. * efficient. The caller should make no assumptions about
  471. * the lifetime of the returned character array, which
  472. * should be copied if needed beyond the use for rendering.
  473. *
  474. * @param offset the starting offset >= 0
  475. * @param length the number of characters to retrieve >= 0
  476. * @param txt the Segment object to retrieve the text into
  477. * @exception BadLocationException the range given includes a position
  478. * that is not a valid position within the document
  479. */
  480. public void getText(int offset, int length, Segment txt) throws BadLocationException {
  481. data.getChars(offset, length, txt);
  482. }
  483. /**
  484. * Returns a position that will track change as the document
  485. * is altered.
  486. * <p>
  487. * This method is thread safe, although most Swing methods
  488. * are not. Please see
  489. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  490. * and Swing</A> for more information.
  491. *
  492. * @param offs the position in the model >= 0
  493. * @return the position
  494. * @exception BadLocationException if the given position does not
  495. * represent a valid location in the associated document
  496. * @see Document#createPosition
  497. */
  498. public synchronized Position createPosition(int offs) throws BadLocationException {
  499. return data.createPosition(offs);
  500. }
  501. /**
  502. * Returns a position that represents the start of the document. The
  503. * position returned can be counted on to track change and stay
  504. * located at the beginning of the document.
  505. *
  506. * @return the position
  507. */
  508. public final Position getStartPosition() {
  509. Position p;
  510. try {
  511. p = createPosition(0);
  512. } catch (BadLocationException bl) {
  513. p = null;
  514. }
  515. return p;
  516. }
  517. /**
  518. * Returns a position that represents the end of the document. The
  519. * position returned can be counted on to track change and stay
  520. * located at the end of the document.
  521. *
  522. * @return the position
  523. */
  524. public final Position getEndPosition() {
  525. Position p;
  526. try {
  527. p = createPosition(data.length());
  528. } catch (BadLocationException bl) {
  529. p = null;
  530. }
  531. return p;
  532. }
  533. /**
  534. * Gets all root elements defined. Typically, there
  535. * will only be one so the default implementation
  536. * is to return the default root element.
  537. *
  538. * @return the root element
  539. */
  540. public Element[] getRootElements() {
  541. Element[] elems = new Element[2];
  542. elems[0] = getDefaultRootElement();
  543. elems[1] = getBidiRootElement();
  544. return elems;
  545. }
  546. /**
  547. * Returns the root element that views should be based upon
  548. * unless some other mechanism for assigning views to element
  549. * structures is provided.
  550. *
  551. * @return the root element
  552. * @see Document#getDefaultRootElement
  553. */
  554. public abstract Element getDefaultRootElement();
  555. // ---- local methods -----------------------------------------
  556. /**
  557. * Returns the root element of the bidirectional structure for this
  558. * document. Its children represent character runs with a given
  559. * Unicode bidi level.
  560. */
  561. public Element getBidiRootElement() {
  562. return bidiRoot;
  563. }
  564. /**
  565. * Returns true if the text in the range <code>p0</code> to
  566. * <code>p1</code> is left to right.
  567. */
  568. boolean isLeftToRight(int p0, int p1) {
  569. Element bidiRoot = getBidiRootElement();
  570. int index = bidiRoot.getElementIndex(p0);
  571. Element bidiElem = bidiRoot.getElement(index);
  572. if(bidiElem.getEndOffset() >= p1) {
  573. AttributeSet bidiAttrs = bidiElem.getAttributes();
  574. return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0);
  575. }
  576. return true;
  577. }
  578. /**
  579. * Get the paragraph element containing the given position. Sub-classes
  580. * must define for themselves what exactly constitutes a paragraph. They
  581. * should keep in mind however that a paragraph should at least be the
  582. * unit of text over which to run the Unicode bidirectional algorithm.
  583. *
  584. * @param pos the starting offset >= 0
  585. * @return the element */
  586. public abstract Element getParagraphElement(int pos);
  587. /**
  588. * Fetches the context for managing attributes. This
  589. * method effectively establishes the strategy used
  590. * for compressing AttributeSet information.
  591. *
  592. * @return the context
  593. */
  594. protected final AttributeContext getAttributeContext() {
  595. return context;
  596. }
  597. /**
  598. * Updates document structure as a result of text insertion. This
  599. * will happen within a write lock. If a subclass of
  600. * this class reimplements this method, it should delegate to the
  601. * superclass as well.
  602. *
  603. * @param chng a description of the change
  604. * @param attr the attributes for the change
  605. */
  606. protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
  607. if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
  608. updateBidi( chng );
  609. }
  610. /**
  611. * Updates any document structure as a result of text removal. This
  612. * method is called before the text is actually removed from the Content.
  613. * This will happen within a write lock. If a subclass
  614. * of this class reimplements this method, it should delegate to the
  615. * superclass as well.
  616. *
  617. * @param chng a description of the change
  618. */
  619. protected void removeUpdate(DefaultDocumentEvent chng) {
  620. }
  621. /**
  622. * Updates any document structure as a result of text removal. This
  623. * method is called after the text has been removed from the Content.
  624. * This will happen within a write lock. If a subclass
  625. * of this class reimplements this method, it should delegate to the
  626. * superclass as well.
  627. *
  628. * @param chng a description of the change
  629. */
  630. protected void postRemoveUpdate(DefaultDocumentEvent chng) {
  631. if( getProperty(I18NProperty).equals( Boolean.TRUE ) )
  632. updateBidi( chng );
  633. }
  634. /**
  635. * Update the bidi element structure as a result of the given change
  636. * to the document. The given change will be updated to reflect the
  637. * changes made to the bidi structure.
  638. *
  639. * This method assumes that every offset in the model is contained in
  640. * exactly one paragraph. This method also assumes that it is called
  641. * after the change is made to the default element structure.
  642. */
  643. private void updateBidi( DefaultDocumentEvent chng ) {
  644. // Calculate the range of paragraphs affected by the change.
  645. int firstPStart;
  646. int lastPEnd;
  647. if( chng.type == DocumentEvent.EventType.INSERT ) {
  648. int chngStart = chng.getOffset();
  649. int chngEnd = chngStart + chng.getLength();
  650. firstPStart = getParagraphElement(chngStart).getStartOffset();
  651. lastPEnd = getParagraphElement(chngEnd).getEndOffset();
  652. } else if( chng.type == DocumentEvent.EventType.REMOVE ) {
  653. Element paragraph = getParagraphElement( chng.getOffset() );
  654. firstPStart = paragraph.getStartOffset();
  655. lastPEnd = paragraph.getEndOffset();
  656. } else {
  657. throw new Error("Internal error: unknown event type.");
  658. }
  659. //System.out.println("updateBidi: firstPStart = " + firstPStart + " lastPEnd = " + lastPEnd );
  660. // Calculate the bidi levels for the affected range of paragraphs. The
  661. // levels array will contain a bidi level for each character in the
  662. // affected text.
  663. byte levels[] = calculateBidiLevels( firstPStart, lastPEnd );
  664. Vector newElements = new Vector();
  665. // Calculate the first span of characters in the affected range with
  666. // the same bidi level. If this level is the same as the level of the
  667. // previous bidi element (the existing bidi element containing
  668. // firstPStart-1), then merge in the previous element. If not, but
  669. // the previous element overlaps the affected range, truncate the
  670. // previous element at firstPStart.
  671. int firstSpanStart = firstPStart;
  672. int removeFromIndex = 0;
  673. if( firstSpanStart > 0 ) {
  674. int prevElemIndex = bidiRoot.getElementIndex(firstPStart-1);
  675. removeFromIndex = prevElemIndex;
  676. Element prevElem = bidiRoot.getElement(prevElemIndex);
  677. int prevLevel=StyleConstants.getBidiLevel(prevElem.getAttributes());
  678. //System.out.println("createbidiElements: prevElem= " + prevElem + " prevLevel= " + prevLevel + "level[0] = " + levels[0]);
  679. if( prevLevel==levels[0] ) {
  680. firstSpanStart = prevElem.getStartOffset();
  681. } else if( prevElem.getEndOffset() > firstPStart ) {
  682. newElements.addElement(new BidiElement(bidiRoot,
  683. prevElem.getStartOffset(),
  684. firstPStart, prevLevel));
  685. } else {
  686. removeFromIndex++;
  687. }
  688. }
  689. int firstSpanEnd = 0;
  690. while((firstSpanEnd<levels.length) && (levels[firstSpanEnd]==levels[0]))
  691. firstSpanEnd++;
  692. // Calculate the last span of characters in the affected range with
  693. // the same bidi level. If this level is the same as the level of the
  694. // next bidi element (the existing bidi element containing lastPEnd),
  695. // then merge in the next element. If not, but the next element
  696. // overlaps the affected range, adjust the next element to start at
  697. // lastPEnd.
  698. int lastSpanEnd = lastPEnd;
  699. Element newNextElem = null;
  700. int removeToIndex = bidiRoot.getElementCount() - 1;
  701. if( lastSpanEnd <= getLength() ) {
  702. int nextElemIndex = bidiRoot.getElementIndex( lastPEnd );
  703. removeToIndex = nextElemIndex;
  704. Element nextElem = bidiRoot.getElement( nextElemIndex );
  705. int nextLevel = StyleConstants.getBidiLevel(nextElem.getAttributes());
  706. if( nextLevel == levels[levels.length-1] ) {
  707. lastSpanEnd = nextElem.getEndOffset();
  708. } else if( nextElem.getStartOffset() < lastPEnd ) {
  709. newNextElem = new BidiElement(bidiRoot, lastPEnd,
  710. nextElem.getEndOffset(),
  711. nextLevel);
  712. } else {
  713. removeToIndex--;
  714. }
  715. }
  716. int lastSpanStart = levels.length;
  717. while( (lastSpanStart>firstSpanEnd)
  718. && (levels[lastSpanStart-1]==levels[levels.length-1]) )
  719. lastSpanStart--;
  720. // If the first and last spans are contiguous and have the same level,
  721. // merge them and create a single new element for the entire span.
  722. // Otherwise, create elements for the first and last spans as well as
  723. // any spans in between.
  724. if((firstSpanEnd==lastSpanStart)&&(levels[0]==levels[levels.length-1])){
  725. newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
  726. lastSpanEnd, levels[0]));
  727. } else {
  728. // Create an element for the first span.
  729. newElements.addElement(new BidiElement(bidiRoot, firstSpanStart,
  730. firstSpanEnd+firstPStart,
  731. levels[0]));
  732. // Create elements for the spans in between the first and last
  733. for( int i=firstSpanEnd; i<lastSpanStart; ) {
  734. //System.out.println("executed line 872");
  735. int j;
  736. for( j=i; (j<levels.length) && (levels[j] == levels[i]); j++ );
  737. newElements.addElement(new BidiElement(bidiRoot, firstPStart+i,
  738. firstPStart+j,
  739. (int)levels[i]));
  740. i=j;
  741. }
  742. // Create an element for the last span.
  743. newElements.addElement(new BidiElement(bidiRoot,
  744. lastSpanStart+firstPStart,
  745. lastSpanEnd,
  746. levels[levels.length-1]));
  747. }
  748. if( newNextElem != null )
  749. newElements.addElement( newNextElem );
  750. // Calculate the set of existing bidi elements which must be
  751. // removed.
  752. int removedElemCount = 0;
  753. if( bidiRoot.getElementCount() > 0 ) {
  754. removedElemCount = removeToIndex - removeFromIndex + 1;
  755. }
  756. Element[] removedElems = new Element[removedElemCount];
  757. for( int i=0; i<removedElemCount; i++ ) {
  758. removedElems[i] = bidiRoot.getElement(removeFromIndex+i);
  759. }
  760. Element[] addedElems = new Element[ newElements.size() ];
  761. newElements.copyInto( addedElems );
  762. // Update the change record.
  763. ElementEdit ee = new ElementEdit( bidiRoot, removeFromIndex,
  764. removedElems, addedElems );
  765. chng.addEdit( ee );
  766. // Update the bidi element structure.
  767. bidiRoot.replace( removeFromIndex, removedElems.length, addedElems );
  768. }
  769. /**
  770. * Calculate the levels array for a range of paragraphs.
  771. */
  772. private byte[] calculateBidiLevels( int firstPStart, int lastPEnd ) {
  773. byte levels[] = new byte[ lastPEnd - firstPStart ];
  774. int levelsEnd = 0;
  775. // For each paragraph in the given range of paragraphs, get its
  776. // levels array and add it to the levels array for the entire span.
  777. for(int o=firstPStart; o<lastPEnd; ) {
  778. Element p = getParagraphElement( o );
  779. int pStart = p.getStartOffset();
  780. int pEnd = p.getEndOffset();
  781. //System.out.println("updateBidi: paragraph start = " + pStart + " paragraph end = " + pEnd);
  782. // Create a Bidi over this paragraph then get the level
  783. // array.
  784. String pText;
  785. try {
  786. pText = getText(pStart, pEnd-pStart);
  787. } catch (BadLocationException e ) {
  788. throw new Error("Internal error: " + e.toString());
  789. }
  790. // REMIND(bcb) we should really be using a Segment here.
  791. Bidi bidiAnalyzer = new Bidi( pText.toCharArray() );
  792. byte[] pLevels = bidiAnalyzer.getLevels();
  793. System.arraycopy( pLevels, 0, levels, levelsEnd, pLevels.length );
  794. levelsEnd += pLevels.length;
  795. o = p.getEndOffset();
  796. }
  797. // REMIND(bcb) remove this code when debugging is done.
  798. if( levelsEnd != levels.length )
  799. throw new Error("levelsEnd assertion failed.");
  800. return levels;
  801. }
  802. /**
  803. * Gives a diagnostic dump.
  804. *
  805. * @param out the output stream
  806. */
  807. public void dump(PrintStream out) {
  808. Element root = getDefaultRootElement();
  809. if (root instanceof AbstractElement) {
  810. ((AbstractElement)root).dump(out, 0);
  811. }
  812. bidiRoot.dump(out,0);
  813. }
  814. /**
  815. * Gets the content for the document.
  816. *
  817. * @return the content
  818. */
  819. protected final Content getContent() {
  820. return data;
  821. }
  822. /**
  823. * Creates a document leaf element.
  824. * Hook through which elements are created to represent the
  825. * document structure. Because this implementation keeps
  826. * structure and content seperate, elements grow automatically
  827. * when content is extended so splits of existing elements
  828. * follow. The document itself gets to decide how to generate
  829. * elements to give flexibility in the type of elements used.
  830. *
  831. * @param parent the parent element
  832. * @param a the attributes for the element
  833. * @param p0 the beginning of the range >= 0
  834. * @param p1 the end of the range >= p0
  835. * @return the new element
  836. */
  837. protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
  838. return new LeafElement(parent, a, p0, p1);
  839. }
  840. /**
  841. * Creates a document branch element, that can contain other elements.
  842. *
  843. * @param parent the parent element
  844. * @param a the attributes
  845. * @return the element
  846. */
  847. protected Element createBranchElement(Element parent, AttributeSet a) {
  848. return new BranchElement(parent, a);
  849. }
  850. // --- Document locking ----------------------------------
  851. /**
  852. * Fetches the current writing thread if there is one.
  853. * This can be used to distinguish whether a method is
  854. * being called as part of an existing modification or
  855. * if a lock needs to be acquired and a new transaction
  856. * started.
  857. *
  858. * @returns the thread actively modifying the document
  859. * or null if there are no modifications in progress
  860. */
  861. protected synchronized final Thread getCurrentWriter() {
  862. return currWriter;
  863. }
  864. /**
  865. * Acquires a lock to begin mutating the document this lock
  866. * protects. There can be no writing, notification of changes, or
  867. * reading going on in order to gain the lock.
  868. *
  869. * @exception IllegalStateException thrown on illegal lock
  870. * attempt. If the document is implemented properly, this can
  871. * only happen if a document listener attempts to mutate the
  872. * document. This situation violates the bean event model
  873. * where order of delivery is not guaranteed and all listeners
  874. * should be notified before further mutations are allowed.
  875. */
  876. protected synchronized final void writeLock() {
  877. try {
  878. while ((numReaders > 0) || (currWriter != null)) {
  879. if (Thread.currentThread() == currWriter) {
  880. // Assuming one doesn't do something wrong in a subclass
  881. // this should only happen if a DocumentListener tries to
  882. // mutate the document.
  883. throw new IllegalStateException("Attempt to mutate in notification");
  884. }
  885. wait();
  886. }
  887. currWriter = Thread.currentThread();
  888. } catch (InterruptedException e) {
  889. // safe to let this pass... write lock not
  890. // held if the thread lands here.
  891. }
  892. }
  893. /**
  894. * Releases the write lock held because the write
  895. * operation is finished. This allows either a new
  896. * writer or readers to aquire a lock.
  897. */
  898. protected synchronized final void writeUnlock() {
  899. currWriter = null;
  900. notify();
  901. }
  902. /**
  903. * Acquires a lock to begin reading some state from the
  904. * document. There can be multiple readers at the same time.
  905. * Writing blocks the readers until notification of the change
  906. * to the listeners has been completed. This method should
  907. * be used very carefully to avoid unintended compromise
  908. * of the document. It should always be balanced with a
  909. * <code>readUnlock</code>.
  910. *
  911. * @see #readUnlock
  912. */
  913. public synchronized final void readLock() {
  914. try {
  915. while (currWriter != null) {
  916. if (currWriter == Thread.currentThread()) {
  917. // writer has full read access.... may try to acquire
  918. // lock in notification
  919. return;
  920. }
  921. wait();
  922. }
  923. numReaders += 1;
  924. } catch (InterruptedException e) {
  925. // safe to let this pass... read lock not
  926. // held if the thread lands here.
  927. }
  928. }
  929. /**
  930. * Does a read unlock. This signals that one
  931. * of the readers is done. If there are no more readers
  932. * then writing can begin again. This should be balanced
  933. * with a readLock, and should occur in a finally statement
  934. * so that the balance is guaranteed. The following is an
  935. * example.
  936. * <pre><code>
  937. try {
  938. readLock();
  939. // do something
  940. } finally {
  941. readUnlock();
  942. }
  943. * </code></pre>
  944. *
  945. * @see #readLock
  946. */
  947. public synchronized final void readUnlock() {
  948. if (currWriter == Thread.currentThread()) {
  949. // writer has full read access.... may try to acquire
  950. // lock in notification
  951. return;
  952. }
  953. if (numReaders <= 0) {
  954. throw new StateInvariantError(BAD_LOCK_STATE);
  955. }
  956. numReaders -= 1;
  957. notify();
  958. }
  959. // --- serialization ---------------------------------------------
  960. private void readObject(ObjectInputStream s)
  961. throws ClassNotFoundException, IOException
  962. {
  963. s.defaultReadObject();
  964. listenerList = new EventListenerList();
  965. // Restore bidi structure
  966. //REMIND(bcb) This creates an initial bidi element to account for
  967. //the \n that exists by default in the content.
  968. bidiRoot = new BidiRootElement();
  969. try {
  970. writeLock();
  971. Element[] p = new Element[1];
  972. p[0] = new BidiElement( bidiRoot, 0, 1, 0 );
  973. bidiRoot.replace(0,0,p);
  974. } finally {
  975. writeUnlock();
  976. }
  977. // At this point bidi root is only partially correct. To fully
  978. // restore it we need access to getDefaultRootElement. But, this
  979. // is created by the subclass and at this point will be null. We
  980. // thus use registerValidation.
  981. s.registerValidation(new ObjectInputValidation() {
  982. public void validateObject() {
  983. try {
  984. writeLock();
  985. DefaultDocumentEvent e = new DefaultDocumentEvent
  986. (0, getLength(),
  987. DocumentEvent.EventType.INSERT);
  988. updateBidi( e );
  989. }
  990. finally {
  991. writeUnlock();
  992. }
  993. }
  994. }, 0);
  995. }
  996. // ----- member variables ------------------------------------------
  997. private transient int numReaders;
  998. private transient Thread currWriter;
  999. /**
  1000. * Storage for document-wide properties.
  1001. */
  1002. private Dictionary documentProperties = null;
  1003. /**
  1004. * The event listener list for the document.
  1005. */
  1006. protected EventListenerList listenerList = new EventListenerList();
  1007. /**
  1008. * Where the text is actually stored, and a set of marks
  1009. * that track change as the document is edited are managed.
  1010. */
  1011. private Content data;
  1012. /**
  1013. * Factory for the attributes. This is the strategy for
  1014. * attribute compression and control of the lifetime of
  1015. * a set of attributes as a collection. This may be shared
  1016. * with other documents.
  1017. */
  1018. private AttributeContext context;
  1019. /**
  1020. * The root of the bidirectional structure for this document. Its children
  1021. * represent character runs with the same Unicode bidi level.
  1022. */
  1023. private transient BranchElement bidiRoot;
  1024. private static final String BAD_LOCK_STATE = "document lock failure";
  1025. /**
  1026. * Error message to indicate a bad location.
  1027. */
  1028. protected static final String BAD_LOCATION = "document location failure";
  1029. /**
  1030. * Name of elements used to represent paragraphs
  1031. */
  1032. public static final String ParagraphElementName = "paragraph";
  1033. /**
  1034. * Name of elements used to represent content
  1035. */
  1036. public static final String ContentElementName = "content";
  1037. /**
  1038. * Name of elements used to hold sections (lines/paragraphs).
  1039. */
  1040. public static final String SectionElementName = "section";
  1041. /**
  1042. * Name of elements used to hold a unidirectional run
  1043. */
  1044. public static final String BidiElementName = "bidi level";
  1045. /**
  1046. * Name of the attribute used to specify element
  1047. * names.
  1048. */
  1049. public static final String ElementNameAttribute = "$ename";
  1050. /**
  1051. * Document property that indicates whether internationalization
  1052. * functions such as text reordering or reshaping should be
  1053. * performed. It is currently turned off while this code is
  1054. * being stabilized. It is also left as a package private for now
  1055. * until its long term benefit is decided.
  1056. */
  1057. static final String I18NProperty = "i18n";
  1058. /**
  1059. * Document property that indicates asynchronous loading is
  1060. * desired, with the thread priority given as the value.
  1061. */
  1062. static final String AsyncLoadPriority = "load priority";
  1063. /**
  1064. * Interface to describe a sequence of character content that
  1065. * can be edited. Implementations may or may not support a
  1066. * history mechanism which will be reflected by whether or not
  1067. * mutations return an UndoableEdit implementation.
  1068. * @see AbstractDocument
  1069. */
  1070. public interface Content {
  1071. /**
  1072. * Creates a position within the content that will
  1073. * track change as the content is mutated.
  1074. *
  1075. * @param offset the offset in the content >= 0
  1076. * @return a Position
  1077. * @exception BadLocationException for an invalid offset
  1078. */
  1079. public Position createPosition(int offset) throws BadLocationException;
  1080. /**
  1081. * Current length of the sequence of character content.
  1082. *
  1083. * @return the length >= 0
  1084. */
  1085. public int length();
  1086. /**
  1087. * Inserts a string of characters into the sequence.
  1088. *
  1089. * @param where Offset into the sequence to make the insertion >= 0.
  1090. * @param str String to insert.
  1091. * @return If the implementation supports a history mechansim,
  1092. * a reference to an Edit implementation will be returned,
  1093. * otherwise null.
  1094. * @exception BadLocationException Thrown if the area covered by
  1095. * the arguments is not contained in the character sequence.
  1096. */
  1097. public UndoableEdit insertString(int where, String str) throws BadLocationException;
  1098. /**
  1099. * Removes some portion of the sequence.
  1100. *
  1101. * @param where The offset into the sequence to make the
  1102. * insertion >= 0.
  1103. * @param nitems The number of items in the sequence to remove >= 0.
  1104. * @return If the implementation supports a history mechansim,
  1105. * a reference to an Edit implementation will be returned,
  1106. * otherwise null.
  1107. * @exception BadLocationException Thrown if the area covered by
  1108. * the arguments is not contained in the character sequence.
  1109. */
  1110. public UndoableEdit remove(int where, int nitems) throws BadLocationException;
  1111. /**
  1112. * Fetches a string of characters contained in the sequence.
  1113. *
  1114. * @param where Offset into the sequence to fetch >= 0.
  1115. * @param len number of characters to copy >= 0.
  1116. * @return the string
  1117. * @exception BadLocationException Thrown if the area covered by
  1118. * the arguments is not contained in the character sequence.
  1119. */
  1120. public String getString(int where, int len) throws BadLocationException;
  1121. /**
  1122. * Gets a sequence of characters and copies them into a Segment.
  1123. *
  1124. * @param where the starting offset >= 0
  1125. * @param len the number of characters >= 0
  1126. * @param txt the target location to copy into
  1127. * @exception BadLocationException Thrown if the area covered by
  1128. * the arguments is not contained in the character sequence.
  1129. */
  1130. public void getChars(int where, int len, Segment txt) throws BadLocationException;
  1131. }
  1132. /**
  1133. * An interface that can be used to allow MutableAttributeSet
  1134. * implementations to use pluggable attribute compression
  1135. * techniques. Each mutation of the attribute set can be
  1136. * used to exchange a previous AttributeSet instance with
  1137. * another, preserving the possibility of the AttributeSet
  1138. * remaining immutable. An implementation is provided by
  1139. * the StyleContext class.
  1140. *
  1141. * The Element implementations provided by this class use
  1142. * this interface to provide their MutableAttributeSet
  1143. * implementations, so that different AttributeSet compression
  1144. * techniques can be employed. The method
  1145. * <code>getAttributeContext</code> should be implemented to
  1146. * return the object responsible for implementing the desired
  1147. * compression technique.
  1148. *
  1149. * @see StyleContext
  1150. */
  1151. public interface AttributeContext {
  1152. /**
  1153. * Adds an attribute to the given set, and returns
  1154. * the new representative set.
  1155. *
  1156. * @param old the old attribute set
  1157. * @param name the non-null attribute name
  1158. * @param value the attribute value
  1159. * @return the updated attribute set
  1160. * @see MutableAttributeSet#addAttribute
  1161. */
  1162. public AttributeSet addAttribute(AttributeSet old, Object name, Object value);
  1163. /**
  1164. * Adds a set of attributes to the element.
  1165. *
  1166. * @param old the old attribute set
  1167. * @param attr the attributes to add
  1168. * @return the updated attribute set
  1169. * @see MutableAttributeSet#addAttribute
  1170. */
  1171. public AttributeSet addAttributes(AttributeSet old, AttributeSet attr);
  1172. /**
  1173. * Removes an attribute from the set.
  1174. *
  1175. * @param old the old attribute set
  1176. * @param name the non-null attribute name
  1177. * @return the updated attribute set
  1178. * @see MutableAttributeSet#removeAttribute
  1179. */
  1180. public AttributeSet removeAttribute(AttributeSet old, Object name);
  1181. /**
  1182. * Removes a set of attributes for the element.
  1183. *
  1184. * @param old the old attribute set
  1185. * @param names the attribute names
  1186. * @return the updated attribute set
  1187. * @see MutableAttributeSet#removeAttributes
  1188. */
  1189. public AttributeSet removeAttributes(AttributeSet old, Enumeration names);
  1190. /**
  1191. * Removes a set of attributes for the element.
  1192. *
  1193. * @param old the old attribute set
  1194. * @param attrs the attributes
  1195. * @return the updated attribute set
  1196. * @see MutableAttributeSet#removeAttributes
  1197. */
  1198. public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs);
  1199. /**
  1200. * Fetches an empty AttributeSet.
  1201. *
  1202. * @return the attribute set
  1203. */
  1204. public AttributeSet getEmptySet();
  1205. /**
  1206. * Reclaims an attribute set.
  1207. * This is a way for a MutableAttributeSet to mark that it no
  1208. * longer need a particular immutable set. This is only necessary
  1209. * in 1.1 where there are no weak references. A 1.1 implementation
  1210. * would call this in its finalize method.
  1211. *
  1212. * @param a the attribute set to reclaim
  1213. */
  1214. public void reclaim(AttributeSet a);
  1215. }
  1216. /**
  1217. * Implements the abstract part of an element. By default elements
  1218. * support attributes by having a field that represents the immutable
  1219. * part of the current attribute set for the element. The element itself
  1220. * implements MutableAttributeSet which can be used to modify the set
  1221. * by fetching a new immutable set. The immutable sets are provided
  1222. * by the AttributeContext associated with the document.
  1223. * <p>
  1224. * <strong>Warning:</strong>
  1225. * Serialized objects of this class will not be compatible with
  1226. * future Swing releases. The current serialization support is appropriate
  1227. * for short term storage or RMI between applications running the same
  1228. * version of Swing. A future release of Swing will provide support for
  1229. * long term persistence.
  1230. */
  1231. public abstract class AbstractElement implements Element, MutableAttributeSet, Serializable, TreeNode {
  1232. /**
  1233. * Creates a new AbstractElement.
  1234. *
  1235. * @param parent the parent element
  1236. * @param a the attributes for the element
  1237. */
  1238. public AbstractElement(Element parent, AttributeSet a) {
  1239. this.parent = parent;
  1240. attributes = getAttributeContext().getEmptySet();
  1241. if (a != null) {
  1242. addAttributes(a);
  1243. }
  1244. }
  1245. private final void indent(PrintWriter out, int n) {
  1246. for (int i = 0; i < n; i++) {
  1247. out.print(" ");
  1248. }
  1249. }
  1250. /**
  1251. * Dumps a debugging representation of the element hierarchy.
  1252. *
  1253. * @param out the output stream
  1254. * @param indentAmount the indentation level >= 0
  1255. */
  1256. public void dump(PrintStream psOut, int indentAmount) {
  1257. PrintWriter out;
  1258. try {
  1259. out = new PrintWriter(new OutputStreamWriter(psOut,"JavaEsc"),
  1260. true);
  1261. } catch (UnsupportedEncodingException e){
  1262. out = new PrintWriter(psOut,true);
  1263. }
  1264. indent(out, indentAmount);
  1265. if (getName() == null) {
  1266. out.print("<??");
  1267. } else {
  1268. out.print("<" + getName());
  1269. }
  1270. if (getAttributeCount() > 0) {
  1271. out.println("");
  1272. // dump the attributes
  1273. Enumeration names = attributes.getAttributeNames();
  1274. while (names.hasMoreElements()) {
  1275. Object name = names.nextElement();
  1276. indent(out, indentAmount + 1);
  1277. out.println(name + "=" + getAttribute(name));
  1278. }
  1279. indent(out, indentAmount);
  1280. }
  1281. out.println(">");
  1282. if (isLeaf()) {
  1283. indent(out, indentAmount+1);
  1284. out.print("[" + getStartOffset() + "," + getEndOffset() + "]");
  1285. Content c = getContent();
  1286. try {
  1287. String contentStr = c.getString(getStartOffset(),
  1288. getEndOffset() - getStartOffset())/*.trim()*/;
  1289. if (contentStr.length() > 40) {
  1290. contentStr = contentStr.substring(0, 40) + "...";
  1291. }
  1292. out.println("["+contentStr+"]");
  1293. } catch (BadLocationException e) {
  1294. ;
  1295. }
  1296. } else {
  1297. int n = getElementCount();
  1298. for (int i = 0; i < n; i++) {
  1299. AbstractElement e = (AbstractElement) getElement(i);
  1300. e.dump(psOut, indentAmount+1);
  1301. }
  1302. }
  1303. }
  1304. // --- Object methods ---------------------------
  1305. /**
  1306. * Finalizes an AbstractElement.
  1307. */
  1308. protected void finalize() throws Throwable {
  1309. AttributeContext context = getAttributeContext();
  1310. context.reclaim(attributes);
  1311. }
  1312. // --- AttributeSet ----------------------------
  1313. // delegated to the immutable field "attributes"
  1314. /**
  1315. * Gets the number of attributes that are defined.
  1316. *
  1317. * @return the number of attributes >= 0
  1318. * @see AttributeSet#getAttributeCount
  1319. */
  1320. public int getAttributeCount() {
  1321. return attributes.getAttributeCount();
  1322. }
  1323. /**
  1324. * Checks whether a given attribute is defined.
  1325. *
  1326. * @param attrName the non-null attribute name
  1327. * @return true if the attribute is defined
  1328. * @see AttributeSet#isDefined
  1329. */
  1330. public boolean isDefined(Object attrName) {
  1331. return attributes.isDefined(attrName);
  1332. }
  1333. /**
  1334. * Checks whether two attribute sets are equal.
  1335. *
  1336. * @param attr the attribute set to check against
  1337. * @return true if the same
  1338. * @see AttributeSet#isEqual
  1339. */
  1340. public boolean isEqual(AttributeSet attr) {
  1341. return attributes.isEqual(attr);
  1342. }
  1343. /**
  1344. * Copies a set of attributes.
  1345. *
  1346. * @return the copy
  1347. * @see AttributeSet#copyAttributes
  1348. */
  1349. public AttributeSet copyAttributes() {
  1350. return attributes.copyAttributes();
  1351. }
  1352. /**
  1353. * Gets the value of an attribute.
  1354. *
  1355. * @param attrName the non-null attribute name
  1356. * @return the attribute value
  1357. * @see AttributeSet#getAttribute
  1358. */
  1359. public Object getAttribute(Object attrName) {
  1360. Object value = attributes.getAttribute(attrName);
  1361. if (value == null) {
  1362. // The delegate nor it's resolvers had a match,
  1363. // so we'll try to resolve through the parent
  1364. // element.
  1365. AttributeSet a = (parent != null) ? parent.getAttributes() : null;
  1366. if (a != null) {
  1367. value = a.getAttribute(attrName);
  1368. }
  1369. }
  1370. return value;
  1371. }
  1372. /**
  1373. * Gets the names of all attributes.
  1374. *
  1375. * @return the attribute names as an enumeration
  1376. * @see AttributeSet#getAttributeNames
  1377. */
  1378. public Enumeration getAttributeNames() {
  1379. return attributes.getAttributeNames();
  1380. }
  1381. /**
  1382. * Checks whether a given attribute name/value is defined.
  1383. *
  1384. * @param name the non-null attribute name
  1385. * @param value the attribute value
  1386. * @return true if the name/value is defined
  1387. * @see AttributeSet#containsAttribute
  1388. */
  1389. public boolean containsAttribute(Object name, Object value) {
  1390. return attributes.containsAttribute(name, value);
  1391. }
  1392. /**
  1393. * Checks whether the element contains all the attributes.
  1394. *
  1395. * @param attrs the attributes to check
  1396. * @return true if the element contains all the attributes
  1397. * @see AttributeSet#containsAttributes
  1398. */
  1399. public boolean containsAttributes(AttributeSet attrs) {
  1400. return attributes.containsAttributes(attrs);
  1401. }
  1402. /**
  1403. * Gets the resolving parent.
  1404. * If not overriden, the resolving parent defaults to
  1405. * the parent element.
  1406. *
  1407. * @return the attributes from the parent, null if none
  1408. * @see AttributeSet#getResolveParent
  1409. */
  1410. public AttributeSet getResolveParent() {
  1411. AttributeSet a = attributes.getResolveParent();
  1412. if ((a == null) && (parent != null)) {
  1413. a = parent.getAttributes();
  1414. }
  1415. return a;
  1416. }
  1417. // --- MutableAttributeSet ----------------------------------
  1418. // should fetch a new immutable record for the field
  1419. // "attributes".
  1420. /**
  1421. * Adds an attribute to the element.
  1422. *
  1423. * @param name the non-null attribute name
  1424. * @param value the attribute value
  1425. * @see MutableAttributeSet#addAttribute
  1426. */
  1427. public void addAttribute(Object name, Object value) {
  1428. checkForIllegalCast();
  1429. AttributeContext context = getAttributeContext();
  1430. attributes = context.addAttribute(attributes, name, value);
  1431. }
  1432. /**
  1433. * Adds a set of attributes to the element.
  1434. *
  1435. * @param attr the attributes to add
  1436. * @see MutableAttributeSet#addAttribute
  1437. */
  1438. public void addAttributes(AttributeSet attr) {
  1439. checkForIllegalCast();
  1440. AttributeContext context = getAttributeContext();
  1441. attributes = context.addAttributes(attributes, attr);
  1442. }
  1443. /**
  1444. * Removes an attribute from the set.
  1445. *
  1446. * @param name the non-null attribute name
  1447. * @see MutableAttributeSet#removeAttribute
  1448. */
  1449. public void removeAttribute(Object name) {
  1450. checkForIllegalCast();
  1451. AttributeContext context = getAttributeContext();
  1452. attributes = context.removeAttribute(attributes, name);
  1453. }
  1454. /**
  1455. * Removes a set of attributes for the element.
  1456. *
  1457. * @param names the attribute names
  1458. * @see MutableAttributeSet#removeAttributes
  1459. */
  1460. public void removeAttributes(Enumeration names) {
  1461. checkForIllegalCast();
  1462. AttributeContext context = getAttributeContext();
  1463. attributes = context.removeAttributes(attributes, names);
  1464. }
  1465. /**
  1466. * Removes a set of attributes for the element.
  1467. *
  1468. * @param attrs the attributes
  1469. * @see MutableAttributeSet#removeAttributes
  1470. */
  1471. public void removeAttributes(AttributeSet attrs) {
  1472. checkForIllegalCast();
  1473. AttributeContext context = getAttributeContext();
  1474. if (attrs == this) {
  1475. attributes = context.getEmptySet();
  1476. } else {
  1477. attributes = context.removeAttributes(attributes, attrs);
  1478. }
  1479. }
  1480. /**
  1481. * Sets the resolving parent.
  1482. *
  1483. * @param parent the parent, null if none
  1484. * @see MutableAttributeSet#setResolveParent
  1485. */
  1486. public void setResolveParent(AttributeSet parent) {
  1487. checkForIllegalCast();
  1488. AttributeContext context = getAttributeContext();
  1489. if (parent != null) {
  1490. attributes =
  1491. context.addAttribute(attributes, StyleConstants.ResolveAttribute,
  1492. parent);
  1493. } else {
  1494. attributes =
  1495. context.removeAttribute(attributes, StyleConstants.ResolveAttribute);
  1496. }
  1497. }
  1498. private final void checkForIllegalCast() {
  1499. Thread t = getCurrentWriter();
  1500. if ((t == null) || (t != Thread.currentThread())) {
  1501. throw new StateInvariantError("Illegal cast to MutableAttributeSet");
  1502. }
  1503. }
  1504. // --- Element methods -------------------------------------
  1505. /**
  1506. * Retrieves the underlying model.
  1507. *
  1508. * @return the model
  1509. */
  1510. public Document getDocument() {
  1511. return AbstractDocument.this;
  1512. }
  1513. /**
  1514. * Gets the parent of the element.
  1515. *
  1516. * @return the parent
  1517. */
  1518. public Element getParentElement() {
  1519. return parent;
  1520. }
  1521. /**
  1522. * Gets the attributes for the element.
  1523. *
  1524. * @return the attribute set
  1525. */
  1526. public AttributeSet getAttributes() {
  1527. return this;
  1528. }
  1529. /**
  1530. * Gets the name of the element.
  1531. *
  1532. * @return the name, null if none
  1533. */
  1534. public String getName() {
  1535. if (attributes.isDefined(ElementNameAttribute)) {
  1536. return (String) attributes.getAttribute(ElementNameAttribute);
  1537. }
  1538. return null;
  1539. }
  1540. /**
  1541. * Gets the starting offset in the model for the element.
  1542. *
  1543. * @return the offset >= 0
  1544. */
  1545. public abstract int getStartOffset();
  1546. /**
  1547. * Gets the ending offset in the model for the element.
  1548. *
  1549. * @return the offset >= 0
  1550. */
  1551. public abstract int getEndOffset();
  1552. /**
  1553. * Gets a child element.
  1554. *
  1555. * @param index the child index, >= 0 && < getElementCount()
  1556. * @return the child element
  1557. */
  1558. public abstract Element getElement(int index);
  1559. /**
  1560. * Gets the number of children for the element.
  1561. *
  1562. * @return the number of children >= 0
  1563. */
  1564. public abstract int getElementCount();
  1565. /**
  1566. * Gets the child element index closest to the given model offset.
  1567. *
  1568. * @param offset the offset >= 0
  1569. * @return the element index >= 0
  1570. */
  1571. public abstract int getElementIndex(int offset);
  1572. /**
  1573. * Checks whether the element is a leaf.
  1574. *
  1575. * @return true if a leaf
  1576. */
  1577. public abstract boolean isLeaf();
  1578. // --- TreeNode methods -------------------------------------
  1579. /**
  1580. * Returns the child <code>TreeNode</code> at index
  1581. * <code>childIndex</code>.
  1582. */
  1583. public TreeNode getChildAt(int childIndex) {
  1584. return (TreeNode)getElement(childIndex);
  1585. }
  1586. /**
  1587. * Returns the number of children <code>TreeNode</code>s the receiver
  1588. * contains.
  1589. */
  1590. public int getChildCount() {
  1591. return getElementCount();
  1592. }
  1593. /**
  1594. * Returns the parent <code>TreeNode</code> of the receiver.
  1595. */
  1596. public TreeNode getParent() {
  1597. return (TreeNode)getParentElement();
  1598. }
  1599. /**
  1600. * Returns the index of <code>node</code> in the receivers children.
  1601. * If the receiver does not contain <code>node</code>, -1 will be
  1602. * returned.
  1603. */
  1604. public int getIndex(TreeNode node) {
  1605. for(int counter = getChildCount() - 1; counter >= 0; counter--)
  1606. if(getChildAt(counter) == node)
  1607. return counter;
  1608. return -1;
  1609. }
  1610. /**
  1611. * Returns true if the receiver allows children.
  1612. */
  1613. public abstract boolean getAllowsChildren();
  1614. /**
  1615. * Returns the children of the reciever as an Enumeration.
  1616. */
  1617. public abstract Enumeration children();
  1618. // --- serialization ---------------------------------------------
  1619. private void writeObject(ObjectOutputStream s) throws IOException {
  1620. s.defaultWriteObject();
  1621. StyleContext.writeAttributeSet(s, attributes);
  1622. }
  1623. private void readObject(ObjectInputStream s)
  1624. throws ClassNotFoundException, IOException
  1625. {
  1626. s.defaultReadObject();
  1627. MutableAttributeSet attr = new SimpleAttributeSet();
  1628. StyleContext.readAttributeSet(s, attr);
  1629. AttributeContext context = getAttributeContext();
  1630. attributes = context.addAttributes(SimpleAttributeSet.EMPTY, attr);
  1631. }
  1632. // ---- variables -----------------------------------------------------
  1633. private Element parent;
  1634. private transient AttributeSet attributes;
  1635. }
  1636. /**
  1637. * Implements a composite element that contains other elements.
  1638. * <p>
  1639. * <strong>Warning:</strong>
  1640. * Serialized objects of this class will not be compatible with
  1641. * future Swing releases. The current serialization support is appropriate
  1642. * for short term storage or RMI between applications running the same
  1643. * version of Swing. A future release of Swing will provide support for
  1644. * long term persistence.
  1645. */
  1646. public class BranchElement extends AbstractElement {
  1647. /**
  1648. * Constructs a composite element that initially contains
  1649. * no children.
  1650. *
  1651. * @param parent The parent element
  1652. * @param a the attributes for the element
  1653. */
  1654. public BranchElement(Element parent, AttributeSet a) {
  1655. super(parent, a);
  1656. children = new AbstractElement[1];
  1657. nchildren = 0;
  1658. lastIndex = -1;
  1659. }
  1660. /**
  1661. * Gets the child element that contains
  1662. * the given model position.
  1663. *
  1664. * @param pos the position >= 0
  1665. * @return the element, null if none
  1666. */
  1667. public Element positionToElement(int pos) {
  1668. int index = getElementIndex(pos);
  1669. Element child = children[index];
  1670. int p0 = child.getStartOffset();
  1671. int p1 = child.getEndOffset();
  1672. if ((pos >= p0) && (pos < p1)) {
  1673. return child;
  1674. }
  1675. return null;
  1676. }
  1677. /**
  1678. * Replaces content with a new set of elements.
  1679. *
  1680. * @param offset the starting offset >= 0
  1681. * @param length the length to replace >= 0
  1682. * @param elems the new elements
  1683. */
  1684. public void replace(int offset, int length, Element[] elems) {
  1685. int delta = elems.length - length;
  1686. int src = offset + length;
  1687. int nmove = nchildren - src;
  1688. int dest = src + delta;
  1689. if ((nchildren + delta) >= children.length) {
  1690. // need to grow the array
  1691. int newLength = Math.max(2*children.length, nchildren + delta);
  1692. AbstractElement[] newChildren = new AbstractElement[newLength];
  1693. System.arraycopy(children, 0, newChildren, 0, offset);
  1694. System.arraycopy(elems, 0, newChildren, offset, elems.length);
  1695. System.arraycopy(children, src, newChildren, dest, nmove);
  1696. children = newChildren;
  1697. } else {
  1698. // patch the existing array
  1699. System.arraycopy(children, src, children, dest, nmove);
  1700. System.arraycopy(elems, 0, children, offset, elems.length);
  1701. }
  1702. nchildren = nchildren + delta;
  1703. }
  1704. /**
  1705. * Converts the element to a string.
  1706. *
  1707. * @return the string
  1708. */
  1709. public String toString() {
  1710. return "BranchElement(" + getName() + ") " + getStartOffset() + "," +
  1711. getEndOffset() + "\n";
  1712. }
  1713. // --- Element methods -----------------------------------
  1714. /**
  1715. * Gets the element name.
  1716. *
  1717. * @return the element name
  1718. */
  1719. public String getName() {
  1720. String nm = super.getName();
  1721. if (nm == null) {
  1722. nm = ParagraphElementName;
  1723. }
  1724. return nm;
  1725. }
  1726. /**
  1727. * Gets the starting offset in the model for the element.
  1728. *
  1729. * @return the offset >= 0
  1730. */
  1731. public int getStartOffset() {
  1732. return children[0].getStartOffset();
  1733. }
  1734. /**
  1735. * Gets the ending offset in the model for the element.
  1736. *
  1737. * @return the offset >= 0
  1738. */
  1739. public int getEndOffset() {
  1740. Element child = children[nchildren - 1];
  1741. return child.getEndOffset();
  1742. }
  1743. /**
  1744. * Gets a child element.
  1745. *
  1746. * @param index the child index, >= 0 && < getElementCount()
  1747. * @return the child element, null if none
  1748. */
  1749. public Element getElement(int index) {
  1750. if (index < nchildren) {
  1751. return children[index];
  1752. }
  1753. return null;
  1754. }
  1755. /**
  1756. * Gets the number of children for the element.
  1757. *
  1758. * @return the number of children >= 0
  1759. */
  1760. public int getElementCount() {
  1761. return nchildren;
  1762. }
  1763. /**
  1764. * Gets the child element index closest to the given model offset.
  1765. *
  1766. * @param offset the offset >= 0
  1767. * @return the element index >= 0
  1768. */
  1769. public int getElementIndex(int offset) {
  1770. int index;
  1771. int lower = 0;
  1772. int upper = nchildren - 1;
  1773. int mid = 0;
  1774. int p0 = getStartOffset();
  1775. int p1;
  1776. if (nchildren == 0) {
  1777. return 0;
  1778. }
  1779. if (offset >= getEndOffset()) {
  1780. return nchildren - 1;
  1781. }
  1782. // see if the last index can be used.
  1783. if ((lastIndex >= lower) && (lastIndex <= upper)) {
  1784. Element lastHit = children[lastIndex];
  1785. p0 = lastHit.getStartOffset();
  1786. p1 = lastHit.getEndOffset();
  1787. if ((offset >= p0) && (offset < p1)) {
  1788. return lastIndex;
  1789. }
  1790. // last index wasn't a hit, but it does give useful info about
  1791. // where a hit (if any) would be.
  1792. if (offset < p0) {
  1793. upper = lastIndex;
  1794. } else {
  1795. lower = lastIndex;
  1796. }
  1797. }
  1798. while (lower <= upper) {
  1799. mid = lower + ((upper - lower) / 2);
  1800. Element elem = children[mid];
  1801. p0 = elem.getStartOffset();
  1802. p1 = elem.getEndOffset();
  1803. if ((offset >= p0) && (offset < p1)) {
  1804. // found the location
  1805. index = mid;
  1806. lastIndex = index;
  1807. return index;
  1808. } else if (offset < p0) {
  1809. upper = mid - 1;
  1810. } else {
  1811. lower = mid + 1;
  1812. }
  1813. }
  1814. // didn't find it, but we indicate the index of where it would belong
  1815. if (offset < p0) {
  1816. index = mid;
  1817. } else {
  1818. index = mid + 1;
  1819. }
  1820. lastIndex = index;
  1821. return index;
  1822. }
  1823. /**
  1824. * Checks whether the element is a leaf.
  1825. *
  1826. * @return true if a leaf
  1827. */
  1828. public boolean isLeaf() {
  1829. return false;
  1830. }
  1831. // ------ TreeNode ----------------------------------------------
  1832. /**
  1833. * Returns true if the receiver allows children.
  1834. */
  1835. public boolean getAllowsChildren() {
  1836. return true;
  1837. }
  1838. /**
  1839. * Returns the children of the reciever as an Enumeration.
  1840. */
  1841. public Enumeration children() {
  1842. if(nchildren == 0)
  1843. return null;
  1844. Vector tempVector = new Vector(nchildren);
  1845. for(int counter = 0; counter < nchildren; counter++)
  1846. tempVector.addElement(children[counter]);
  1847. return tempVector.elements();
  1848. }
  1849. // ------ members ----------------------------------------------
  1850. private AbstractElement[] children;
  1851. private int nchildren;
  1852. private int lastIndex;
  1853. }
  1854. /**
  1855. * Implements an element that directly represents content of
  1856. * some kind.
  1857. * <p>
  1858. * <strong>Warning:</strong>
  1859. * Serialized objects of this class will not be compatible with
  1860. * future Swing releases. The current serialization support is appropriate
  1861. * for short term storage or RMI between applications running the same
  1862. * version of Swing. A future release of Swing will provide support for
  1863. * long term persistence.
  1864. *
  1865. * @see Element
  1866. */
  1867. public class LeafElement extends AbstractElement {
  1868. /**
  1869. * Constructs an element that represents content within the
  1870. * document (has no children).
  1871. *
  1872. * @param parent The parent element
  1873. * @param a The element attributes
  1874. * @param offs0 The start offset >= 0
  1875. * @param offs1 The end offset >= offs0
  1876. */
  1877. public LeafElement(Element parent, AttributeSet a, int offs0, int offs1) {
  1878. super(parent, a);
  1879. try {
  1880. p0 = createPosition(offs0);
  1881. p1 = createPosition(offs1);
  1882. } catch (BadLocationException e) {
  1883. p0 = null;
  1884. p1 = null;
  1885. throw new StateInvariantError("Can't create Position references");
  1886. }
  1887. }
  1888. /**
  1889. * Converts the element to a string.
  1890. *
  1891. * @return the string
  1892. */
  1893. public String toString() {
  1894. return "LeafElement(" + getName() + ") " + p0 + "," + p1 + "\n";
  1895. }
  1896. // --- Element methods ---------------------------------------------
  1897. /**
  1898. * Gets the starting offset in the model for the element.
  1899. *
  1900. * @return the offset >= 0
  1901. */
  1902. public int getStartOffset() {
  1903. return p0.getOffset();
  1904. }
  1905. /**
  1906. * Gets the ending offset in the model for the element.
  1907. *
  1908. * @return the offset >= 0
  1909. */
  1910. public int getEndOffset() {
  1911. return p1.getOffset();
  1912. }
  1913. /**
  1914. * Gets the element name.
  1915. *
  1916. * @return the name
  1917. */
  1918. public String getName() {
  1919. String nm = super.getName();
  1920. if (nm == null) {
  1921. nm = ContentElementName;
  1922. }
  1923. return nm;
  1924. }
  1925. /**
  1926. * Gets the child element index closest to the given model offset.
  1927. *
  1928. * @param pos the offset >= 0
  1929. * @return the element index >= 0
  1930. */
  1931. public int getElementIndex(int pos) {
  1932. return -1;
  1933. }
  1934. /**
  1935. * Gets a child element.
  1936. *
  1937. * @param index the child index, >= 0 && < getElementCount()
  1938. * @return the child element
  1939. */
  1940. public Element getElement(int index) {
  1941. return null;
  1942. }
  1943. /**
  1944. * Returns the number of child elements.
  1945. *
  1946. * @return the number of children >= 0
  1947. */
  1948. public int getElementCount() {
  1949. return 0;
  1950. }
  1951. /**
  1952. * Checks whether the element is a leaf.
  1953. *
  1954. * @return true if a leaf
  1955. */
  1956. public boolean isLeaf() {
  1957. return true;
  1958. }
  1959. // ------ TreeNode ----------------------------------------------
  1960. /**
  1961. * Returns true if the receiver allows children.
  1962. */
  1963. public boolean getAllowsChildren() {
  1964. return false;
  1965. }
  1966. /**
  1967. * Returns the children of the reciever as an Enumeration.
  1968. */
  1969. public Enumeration children() {
  1970. return null;
  1971. }
  1972. // --- serialization ---------------------------------------------
  1973. private void writeObject(ObjectOutputStream s) throws IOException {
  1974. s.defaultWriteObject();
  1975. s.writeInt(p0.getOffset());
  1976. s.writeInt(p1.getOffset());
  1977. }
  1978. private void readObject(ObjectInputStream s)
  1979. throws ClassNotFoundException, IOException
  1980. {
  1981. s.defaultReadObject();
  1982. // set the range with positions that track change
  1983. int off0 = s.readInt();
  1984. int off1 = s.readInt();
  1985. try {
  1986. p0 = createPosition(off0);
  1987. p1 = createPosition(off1);
  1988. } catch (BadLocationException e) {
  1989. p0 = null;
  1990. p1 = null;
  1991. throw new IOException("Can't restore Position references");
  1992. }
  1993. }
  1994. // ---- members -----------------------------------------------------
  1995. private transient Position p0;
  1996. private transient Position p1;
  1997. }
  1998. /**
  1999. * Represents the root element of the bidirectional element structure.
  2000. * The root element is the only element in the bidi element structure
  2001. * which contains children.
  2002. */
  2003. class BidiRootElement extends BranchElement {
  2004. BidiRootElement() {
  2005. super( null, null );
  2006. }
  2007. /**
  2008. * Gets the name of the element.
  2009. * @return the name
  2010. */
  2011. public String getName() {
  2012. return "bidi root";
  2013. }
  2014. }
  2015. /**
  2016. * Represents an element of the bidirectional element structure.
  2017. */
  2018. class BidiElement extends LeafElement {
  2019. /**
  2020. * Creates a new BidiElement.
  2021. */
  2022. BidiElement(Element parent, int start, int end, int level) {
  2023. super(parent, new SimpleAttributeSet(), start, end);
  2024. addAttribute(StyleConstants.BidiLevel, new Integer(level));
  2025. //System.out.println("BidiElement: start = " + start
  2026. // + " end = " + end + " level = " + level );
  2027. }
  2028. /**
  2029. * Gets the name of the element.
  2030. * @return the name
  2031. */
  2032. public String getName() {
  2033. return BidiElementName;
  2034. }
  2035. int getLevel() {
  2036. Integer o = (Integer) getAttribute(StyleConstants.BidiLevel);
  2037. if (o != null) {
  2038. return o.intValue();
  2039. }
  2040. return 0; // Level 0 is base level (non-embedded) left-to-right
  2041. }
  2042. boolean isLeftToRight() {
  2043. return ((getLevel() % 2) == 0);
  2044. }
  2045. }
  2046. /**
  2047. * Stores document changes as the document is being
  2048. * modified. Can subsequently be used for change notification
  2049. * when done with the document modification transaction.
  2050. * This is used by the AbstractDocument class and its extensions
  2051. * for broadcasting change information to the document listeners.
  2052. */
  2053. public class DefaultDocumentEvent extends CompoundEdit implements DocumentEvent {
  2054. /**
  2055. * Constructs a change record.
  2056. *
  2057. * @param offs the offset into the document of the change >= 0
  2058. * @param len the length of the change >= 0
  2059. * @param type the type of event (DocumentEvent.EventType)
  2060. */
  2061. public DefaultDocumentEvent(int offs, int len, DocumentEvent.EventType type) {
  2062. super();
  2063. offset = offs;
  2064. length = len;
  2065. this.type = type;
  2066. }
  2067. /**
  2068. * Returns a string description of the change event.
  2069. *
  2070. * @return a string
  2071. */
  2072. public String toString() {
  2073. return edits.toString();
  2074. }
  2075. // --- CompoundEdit methods --------------------------
  2076. /**
  2077. * Adds a document edit. If the number of edits crosses
  2078. * a threshold, this switches on a hashtable lookup for
  2079. * ElementChange implementations since access of these
  2080. * needs to be relatively quick.
  2081. *
  2082. * @param anEdit a document edit record
  2083. * @return true if the edit was added
  2084. */
  2085. public boolean addEdit(UndoableEdit anEdit) {
  2086. // if the number of changes gets too great, start using
  2087. // a hashtable for to locate the change for a given element.
  2088. if ((changeLookup == null) && (edits.size() > 10)) {
  2089. changeLookup = new Hashtable();
  2090. int n = edits.size();
  2091. for (int i = 0; i < n; i++) {
  2092. Object o = edits.elementAt(i);
  2093. if (o instanceof DocumentEvent.ElementChange) {
  2094. DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) o;
  2095. changeLookup.put(ec.getElement(), ec);
  2096. }
  2097. }
  2098. }
  2099. // if we have a hashtable... add the entry if it's
  2100. // an ElementChange.
  2101. if ((changeLookup != null) && (anEdit instanceof DocumentEvent.ElementChange)) {
  2102. DocumentEvent.ElementChange ec = (DocumentEvent.ElementChange) anEdit;
  2103. changeLookup.put(ec.getElement(), ec);
  2104. }
  2105. return super.addEdit(anEdit);
  2106. }
  2107. /**
  2108. * Redoes a change.
  2109. *
  2110. * @exception CannotRedoException if the change cannot be redone
  2111. */
  2112. public void redo() throws CannotRedoException {
  2113. writeLock();
  2114. try {
  2115. // change the state
  2116. super.redo();
  2117. // fire a DocumentEvent to notify the view(s)
  2118. if (type == DocumentEvent.EventType.INSERT) {
  2119. fireInsertUpdate(this);
  2120. } else if (type == DocumentEvent.EventType.REMOVE) {
  2121. fireRemoveUpdate(this);
  2122. } else {
  2123. fireChangedUpdate(this);
  2124. }
  2125. } finally {
  2126. writeUnlock();
  2127. }
  2128. }
  2129. /**
  2130. * Undoes a change.
  2131. *
  2132. * @exception CannotUndoException if the change cannot be undone
  2133. */
  2134. public void undo() throws CannotUndoException {
  2135. writeLock();
  2136. try {
  2137. // change the state
  2138. super.undo();
  2139. // fire a DocumentEvent to notify the view(s)
  2140. if (type == DocumentEvent.EventType.REMOVE) {
  2141. fireInsertUpdate(this);
  2142. } else if (type == DocumentEvent.EventType.INSERT) {
  2143. fireRemoveUpdate(this);
  2144. } else {
  2145. fireChangedUpdate(this);
  2146. }
  2147. } finally {
  2148. writeUnlock();
  2149. }
  2150. }
  2151. /**
  2152. * DefaultDocument events are significant. If you wish to aggregate
  2153. * DefaultDocumentEvents to present them as a single edit to the user
  2154. * place them into a CompoundEdit.
  2155. *
  2156. * @return whether the event is significant for edit undo purposes
  2157. */
  2158. public boolean isSignificant() {
  2159. return true;
  2160. }
  2161. /**
  2162. * Provides a localized, human readable description of this edit
  2163. * suitable for use in, say, a change log.
  2164. *
  2165. * @return the description
  2166. */
  2167. public String getPresentationName() {
  2168. DocumentEvent.EventType type = getType();
  2169. if(type == DocumentEvent.EventType.INSERT)
  2170. return "addition";
  2171. if(type == DocumentEvent.EventType.REMOVE)
  2172. return "deletion";
  2173. return "style change";
  2174. }
  2175. /**
  2176. * Provides a localized, human readable description of the undoable
  2177. * form of this edit, e.g. for use as an Undo menu item. Typically
  2178. * derived from getDescription();
  2179. *
  2180. * @return the description
  2181. */
  2182. public String getUndoPresentationName() {
  2183. return "Undo " + getPresentationName();
  2184. }
  2185. /**
  2186. * Provides a localized, human readable description of the redoable
  2187. * form of this edit, e.g. for use as a Redo menu item. Typically
  2188. * derived from getPresentationName();
  2189. *
  2190. * @return the description
  2191. */
  2192. public String getRedoPresentationName() {
  2193. return "Redo " + getPresentationName();
  2194. }
  2195. // --- DocumentEvent methods --------------------------
  2196. /**
  2197. * Returns the type of event.
  2198. *
  2199. * @return the event type as a DocumentEvent.EventType
  2200. * @see DocumentEvent#getType
  2201. */
  2202. public DocumentEvent.EventType getType() {
  2203. return type;
  2204. }
  2205. /**
  2206. * Returns the offset within the document of the start of the change.
  2207. *
  2208. * @return the offset >= 0
  2209. * @see DocumentEvent#getOffset
  2210. */
  2211. public int getOffset() {
  2212. return offset;
  2213. }
  2214. /**
  2215. * Returns the length of the change.
  2216. *
  2217. * @return the length >= 0
  2218. * @see DocumentEvent#getLength
  2219. */
  2220. public int getLength() {
  2221. return length;
  2222. }
  2223. /**
  2224. * Gets the document that sourced the change event.
  2225. *
  2226. * @return the document
  2227. * @see DocumentEvent#getDocument
  2228. */
  2229. public Document getDocument() {
  2230. return AbstractDocument.this;
  2231. }
  2232. /**
  2233. * Gets the changes for an element.
  2234. *
  2235. * @param elem the element
  2236. * @return the changes
  2237. */
  2238. public DocumentEvent.ElementChange getChange(Element elem) {
  2239. if (changeLookup != null) {
  2240. return (DocumentEvent.ElementChange) changeLookup.get(elem);
  2241. }
  2242. int n = edits.size();
  2243. for (int i = 0; i < n; i++) {
  2244. Object o = edits.elementAt(i);
  2245. if (o instanceof DocumentEvent.ElementChange) {
  2246. DocumentEvent.ElementChange c = (DocumentEvent.ElementChange) o;
  2247. if (c.getElement() == elem) {
  2248. return c;
  2249. }
  2250. }
  2251. }
  2252. return null;
  2253. }
  2254. // --- member variables ------------------------------------
  2255. private int offset;
  2256. private int length;
  2257. private Hashtable changeLookup;
  2258. private DocumentEvent.EventType type;
  2259. }
  2260. /**
  2261. * An implementation of ElementChange that can be added to the document
  2262. * event.
  2263. */
  2264. public static class ElementEdit extends AbstractUndoableEdit implements DocumentEvent.ElementChange {
  2265. /**
  2266. * Constructs an edit record. This does not modify the element
  2267. * so it can safely be used to <em>catch up</em> a view to the
  2268. * current model state for views that just attached to a model.
  2269. *
  2270. * @param e the element
  2271. * @param index the index into the model >= 0
  2272. * @param removed a set of elements that were removed
  2273. * @param added a set of elements that were added
  2274. */
  2275. public ElementEdit(Element e, int index, Element[] removed, Element[] added) {
  2276. super();
  2277. this.e = e;
  2278. this.index = index;
  2279. this.removed = removed;
  2280. this.added = added;
  2281. }
  2282. /**
  2283. * Returns the underlying element.
  2284. *
  2285. * @return the element
  2286. */
  2287. public Element getElement() {
  2288. return e;
  2289. }
  2290. /**
  2291. * Returns the index into the list of elements.
  2292. *
  2293. * @return the index >= 0
  2294. */
  2295. public int getIndex() {
  2296. return index;
  2297. }
  2298. /**
  2299. * Gets a list of children that were removed.
  2300. *
  2301. * @return the list
  2302. */
  2303. public Element[] getChildrenRemoved() {
  2304. return removed;
  2305. }
  2306. /**
  2307. * Gets a list of children that were added.
  2308. *
  2309. * @return the list
  2310. */
  2311. public Element[] getChildrenAdded() {
  2312. return added;
  2313. }
  2314. /**
  2315. * Redoes a change.
  2316. *
  2317. * @exception CannotRedoException if the change cannot be redone
  2318. */
  2319. public void redo() throws CannotRedoException {
  2320. super.redo();
  2321. // Since this event will be reused, switch around added/removed.
  2322. Element[] tmp = removed;
  2323. removed = added;
  2324. added = tmp;
  2325. // PENDING(prinz) need MutableElement interface, canRedo() should check
  2326. ((AbstractDocument.BranchElement)e).replace(index, removed.length, added);
  2327. }
  2328. /**
  2329. * Undoes a change.
  2330. *
  2331. * @exception CannotUndoException if the change cannot be undone
  2332. */
  2333. public void undo() throws CannotUndoException {
  2334. super.undo();
  2335. // PENDING(prinz) need MutableElement interface, canUndo() should check
  2336. ((AbstractDocument.BranchElement)e).replace(index, added.length, removed);
  2337. // Since this event will be reused, switch around added/removed.
  2338. Element[] tmp = removed;
  2339. removed = added;
  2340. added = tmp;
  2341. }
  2342. private Element e;
  2343. private int index;
  2344. private Element[] removed;
  2345. private Element[] added;
  2346. }
  2347. }