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