Print this page
Split |
Close |
Expand all |
Collapse all |
--- old/src/share/classes/javax/swing/text/DefaultStyledDocument.java
+++ new/src/share/classes/javax/swing/text/DefaultStyledDocument.java
1 1 /*
2 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 4 *
5 5 * This code is free software; you can redistribute it and/or modify it
6 6 * under the terms of the GNU General Public License version 2 only, as
7 7 * published by the Free Software Foundation. Oracle designates this
8 8 * particular file as subject to the "Classpath" exception as provided
9 9 * by Oracle in the LICENSE file that accompanied this code.
10 10 *
11 11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 14 * version 2 for more details (a copy is included in the LICENSE file that
15 15 * accompanied this code).
16 16 *
17 17 * You should have received a copy of the GNU General Public License version
18 18 * 2 along with this work; if not, write to the Free Software Foundation,
19 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 20 *
21 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 22 * or visit www.oracle.com if you need additional information or have any
23 23 * questions.
24 24 */
25 25 package javax.swing.text;
26 26
27 27 import java.awt.Color;
28 28 import java.awt.Font;
29 29 import java.awt.font.TextAttribute;
30 30 import java.lang.ref.ReferenceQueue;
31 31 import java.lang.ref.WeakReference;
32 32 import java.util.Enumeration;
33 33 import java.util.HashMap;
34 34 import java.util.List;
35 35 import java.util.Map;
36 36 import java.util.Stack;
37 37 import java.util.Vector;
38 38 import java.util.ArrayList;
39 39 import java.io.IOException;
40 40 import java.io.ObjectInputStream;
41 41 import java.io.Serializable;
42 42 import javax.swing.event.*;
43 43 import javax.swing.undo.AbstractUndoableEdit;
44 44 import javax.swing.undo.CannotRedoException;
45 45 import javax.swing.undo.CannotUndoException;
46 46 import javax.swing.undo.UndoableEdit;
47 47 import javax.swing.SwingUtilities;
48 48 import static sun.swing.SwingUtilities2.IMPLIED_CR;
49 49
50 50 /**
51 51 * A document that can be marked up with character and paragraph
52 52 * styles in a manner similar to the Rich Text Format. The element
53 53 * structure for this document represents style crossings for
54 54 * style runs. These style runs are mapped into a paragraph element
55 55 * structure (which may reside in some other structure). The
56 56 * style runs break at paragraph boundaries since logical styles are
57 57 * assigned to paragraph boundaries.
58 58 * <p>
59 59 * <strong>Warning:</strong>
60 60 * Serialized objects of this class will not be compatible with
61 61 * future Swing releases. The current serialization support is
62 62 * appropriate for short term storage or RMI between applications running
63 63 * the same version of Swing. As of 1.4, support for long term storage
64 64 * of all JavaBeans™
65 65 * has been added to the <code>java.beans</code> package.
66 66 * Please see {@link java.beans.XMLEncoder}.
67 67 *
68 68 * @author Timothy Prinzing
69 69 * @see Document
70 70 * @see AbstractDocument
71 71 */
72 72 public class DefaultStyledDocument extends AbstractDocument implements StyledDocument {
73 73
74 74 /**
75 75 * Constructs a styled document.
76 76 *
77 77 * @param c the container for the content
78 78 * @param styles resources and style definitions which may
79 79 * be shared across documents
80 80 */
81 81 public DefaultStyledDocument(Content c, StyleContext styles) {
82 82 super(c, styles);
83 83 listeningStyles = new Vector<Style>();
84 84 buffer = new ElementBuffer(createDefaultRoot());
85 85 Style defaultStyle = styles.getStyle(StyleContext.DEFAULT_STYLE);
86 86 setLogicalStyle(0, defaultStyle);
87 87 }
88 88
89 89 /**
90 90 * Constructs a styled document with the default content
91 91 * storage implementation and a shared set of styles.
92 92 *
93 93 * @param styles the styles
94 94 */
95 95 public DefaultStyledDocument(StyleContext styles) {
96 96 this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
97 97 }
98 98
99 99 /**
100 100 * Constructs a default styled document. This buffers
101 101 * input content by a size of <em>BUFFER_SIZE_DEFAULT</em>
102 102 * and has a style context that is scoped by the lifetime
103 103 * of the document and is not shared with other documents.
104 104 */
105 105 public DefaultStyledDocument() {
106 106 this(new GapContent(BUFFER_SIZE_DEFAULT), new StyleContext());
107 107 }
108 108
109 109 /**
110 110 * Gets the default root element.
111 111 *
112 112 * @return the root
113 113 * @see Document#getDefaultRootElement
114 114 */
115 115 public Element getDefaultRootElement() {
116 116 return buffer.getRootElement();
117 117 }
118 118
119 119 /**
120 120 * Initialize the document to reflect the given element
121 121 * structure (i.e. the structure reported by the
122 122 * <code>getDefaultRootElement</code> method. If the
123 123 * document contained any data it will first be removed.
124 124 */
125 125 protected void create(ElementSpec[] data) {
126 126 try {
127 127 if (getLength() != 0) {
128 128 remove(0, getLength());
129 129 }
130 130 writeLock();
131 131
132 132 // install the content
133 133 Content c = getContent();
134 134 int n = data.length;
135 135 StringBuilder sb = new StringBuilder();
136 136 for (int i = 0; i < n; i++) {
137 137 ElementSpec es = data[i];
138 138 if (es.getLength() > 0) {
139 139 sb.append(es.getArray(), es.getOffset(), es.getLength());
140 140 }
141 141 }
142 142 UndoableEdit cEdit = c.insertString(0, sb.toString());
143 143
144 144 // build the event and element structure
145 145 int length = sb.length();
146 146 DefaultDocumentEvent evnt =
147 147 new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT);
148 148 evnt.addEdit(cEdit);
149 149 buffer.create(length, data, evnt);
150 150
151 151 // update bidi (possibly)
152 152 super.insertUpdate(evnt, null);
153 153
154 154 // notify the listeners
155 155 evnt.end();
156 156 fireInsertUpdate(evnt);
157 157 fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
158 158 } catch (BadLocationException ble) {
159 159 throw new StateInvariantError("problem initializing");
160 160 } finally {
161 161 writeUnlock();
162 162 }
163 163
164 164 }
165 165
↓ open down ↓ |
165 lines elided |
↑ open up ↑ |
166 166 /**
167 167 * Inserts new elements in bulk. This is useful to allow
168 168 * parsing with the document in an unlocked state and
169 169 * prepare an element structure modification. This method
170 170 * takes an array of tokens that describe how to update an
171 171 * element structure so the time within a write lock can
172 172 * be greatly reduced in an asynchronous update situation.
173 173 * <p>
174 174 * This method is thread safe, although most Swing methods
175 175 * are not. Please see
176 - * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
176 + * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
177 177 * in Swing</A> for more information.
178 178 *
179 179 * @param offset the starting offset >= 0
180 180 * @param data the element data
181 181 * @exception BadLocationException for an invalid starting offset
182 182 */
183 183 protected void insert(int offset, ElementSpec[] data) throws BadLocationException {
184 184 if (data == null || data.length == 0) {
185 185 return;
186 186 }
187 187
188 188 try {
189 189 writeLock();
190 190
191 191 // install the content
192 192 Content c = getContent();
193 193 int n = data.length;
194 194 StringBuilder sb = new StringBuilder();
195 195 for (int i = 0; i < n; i++) {
196 196 ElementSpec es = data[i];
197 197 if (es.getLength() > 0) {
198 198 sb.append(es.getArray(), es.getOffset(), es.getLength());
199 199 }
200 200 }
201 201 if (sb.length() == 0) {
202 202 // Nothing to insert, bail.
203 203 return;
204 204 }
205 205 UndoableEdit cEdit = c.insertString(offset, sb.toString());
206 206
207 207 // create event and build the element structure
208 208 int length = sb.length();
209 209 DefaultDocumentEvent evnt =
210 210 new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT);
211 211 evnt.addEdit(cEdit);
212 212 buffer.insert(offset, length, data, evnt);
213 213
214 214 // update bidi (possibly)
215 215 super.insertUpdate(evnt, null);
216 216
217 217 // notify the listeners
218 218 evnt.end();
219 219 fireInsertUpdate(evnt);
220 220 fireUndoableEditUpdate(new UndoableEditEvent(this, evnt));
221 221 } finally {
222 222 writeUnlock();
223 223 }
224 224 }
225 225
226 226 /**
227 227 * Removes an element from this document.
228 228 *
229 229 * <p>The element is removed from its parent element, as well as
230 230 * the text in the range identified by the element. If the
231 231 * element isn't associated with the document, {@code
232 232 * IllegalArgumentException} is thrown.</p>
233 233 *
234 234 * <p>As empty branch elements are not allowed in the document, if the
235 235 * element is the sole child, its parent element is removed as well,
236 236 * recursively. This means that when replacing all the children of a
237 237 * particular element, new children should be added <em>before</em>
238 238 * removing old children.
239 239 *
240 240 * <p>Element removal results in two events being fired, the
241 241 * {@code DocumentEvent} for changes in element structure and {@code
242 242 * UndoableEditEvent} for changes in document content.</p>
243 243 *
244 244 * <p>If the element contains end-of-content mark (the last {@code
245 245 * "\n"} character in document), this character is not removed;
246 246 * instead, preceding leaf element is extended to cover the
247 247 * character. If the last leaf already ends with {@code "\n",} it is
248 248 * included in content removal.</p>
249 249 *
250 250 * <p>If the element is {@code null,} {@code NullPointerException} is
251 251 * thrown. If the element structure would become invalid after the removal,
252 252 * for example if the element is the document root element, {@code
253 253 * IllegalArgumentException} is thrown. If the current element structure is
254 254 * invalid, {@code IllegalStateException} is thrown.</p>
255 255 *
256 256 * @param elem the element to remove
257 257 * @throws NullPointerException if the element is {@code null}
258 258 * @throws IllegalArgumentException if the element could not be removed
259 259 * @throws IllegalStateException if the element structure is invalid
260 260 *
261 261 * @since 1.7
262 262 */
263 263 public void removeElement(Element elem) {
264 264 try {
265 265 writeLock();
266 266 removeElementImpl(elem);
267 267 } finally {
268 268 writeUnlock();
269 269 }
270 270 }
271 271
272 272 private void removeElementImpl(Element elem) {
273 273 if (elem.getDocument() != this) {
274 274 throw new IllegalArgumentException("element doesn't belong to document");
275 275 }
276 276 BranchElement parent = (BranchElement) elem.getParentElement();
277 277 if (parent == null) {
278 278 throw new IllegalArgumentException("can't remove the root element");
279 279 }
280 280
281 281 int startOffset = elem.getStartOffset();
282 282 int removeFrom = startOffset;
283 283 int endOffset = elem.getEndOffset();
284 284 int removeTo = endOffset;
285 285 int lastEndOffset = getLength() + 1;
286 286 Content content = getContent();
287 287 boolean atEnd = false;
288 288 boolean isComposedText = Utilities.isComposedTextElement(elem);
289 289
290 290 if (endOffset >= lastEndOffset) {
291 291 // element includes the last "\n" character, needs special handling
292 292 if (startOffset <= 0) {
293 293 throw new IllegalArgumentException("can't remove the whole content");
294 294 }
295 295 removeTo = lastEndOffset - 1; // last "\n" must not be removed
296 296 try {
297 297 if (content.getString(startOffset - 1, 1).charAt(0) == '\n') {
298 298 removeFrom--; // preceding leaf ends with "\n", remove it
299 299 }
300 300 } catch (BadLocationException ble) { // can't happen
301 301 throw new IllegalStateException(ble);
302 302 }
303 303 atEnd = true;
304 304 }
305 305 int length = removeTo - removeFrom;
306 306
307 307 DefaultDocumentEvent dde = new DefaultDocumentEvent(removeFrom,
308 308 length, DefaultDocumentEvent.EventType.REMOVE);
309 309 UndoableEdit ue = null;
310 310 // do not leave empty branch elements
311 311 while (parent.getElementCount() == 1) {
312 312 elem = parent;
313 313 parent = (BranchElement) parent.getParentElement();
314 314 if (parent == null) { // shouldn't happen
315 315 throw new IllegalStateException("invalid element structure");
316 316 }
317 317 }
318 318 Element[] removed = { elem };
319 319 Element[] added = {};
320 320 int index = parent.getElementIndex(startOffset);
321 321 parent.replace(index, 1, added);
322 322 dde.addEdit(new ElementEdit(parent, index, removed, added));
323 323 if (length > 0) {
324 324 try {
325 325 ue = content.remove(removeFrom, length);
326 326 if (ue != null) {
327 327 dde.addEdit(ue);
328 328 }
329 329 } catch (BadLocationException ble) {
330 330 // can only happen if the element structure is severely broken
331 331 throw new IllegalStateException(ble);
332 332 }
333 333 lastEndOffset -= length;
334 334 }
335 335
336 336 if (atEnd) {
337 337 // preceding leaf element should be extended to cover orphaned "\n"
338 338 Element prevLeaf = parent.getElement(parent.getElementCount() - 1);
339 339 while ((prevLeaf != null) && !prevLeaf.isLeaf()) {
340 340 prevLeaf = prevLeaf.getElement(prevLeaf.getElementCount() - 1);
341 341 }
342 342 if (prevLeaf == null) { // shouldn't happen
343 343 throw new IllegalStateException("invalid element structure");
344 344 }
345 345 int prevStartOffset = prevLeaf.getStartOffset();
346 346 BranchElement prevParent = (BranchElement) prevLeaf.getParentElement();
347 347 int prevIndex = prevParent.getElementIndex(prevStartOffset);
348 348 Element newElem;
349 349 newElem = createLeafElement(prevParent, prevLeaf.getAttributes(),
350 350 prevStartOffset, lastEndOffset);
351 351 Element[] prevRemoved = { prevLeaf };
352 352 Element[] prevAdded = { newElem };
353 353 prevParent.replace(prevIndex, 1, prevAdded);
354 354 dde.addEdit(new ElementEdit(prevParent, prevIndex,
355 355 prevRemoved, prevAdded));
356 356 }
357 357
358 358 postRemoveUpdate(dde);
359 359 dde.end();
360 360 fireRemoveUpdate(dde);
361 361 if (! (isComposedText && (ue != null))) {
362 362 // do not fire UndoabeEdit event for composed text edit (unsupported)
363 363 fireUndoableEditUpdate(new UndoableEditEvent(this, dde));
364 364 }
365 365 }
366 366
367 367 /**
368 368 * Adds a new style into the logical style hierarchy. Style attributes
369 369 * resolve from bottom up so an attribute specified in a child
370 370 * will override an attribute specified in the parent.
371 371 *
372 372 * @param nm the name of the style (must be unique within the
373 373 * collection of named styles). The name may be null if the style
374 374 * is unnamed, but the caller is responsible
375 375 * for managing the reference returned as an unnamed style can't
376 376 * be fetched by name. An unnamed style may be useful for things
377 377 * like character attribute overrides such as found in a style
378 378 * run.
379 379 * @param parent the parent style. This may be null if unspecified
380 380 * attributes need not be resolved in some other style.
381 381 * @return the style
382 382 */
383 383 public Style addStyle(String nm, Style parent) {
384 384 StyleContext styles = (StyleContext) getAttributeContext();
385 385 return styles.addStyle(nm, parent);
386 386 }
387 387
388 388 /**
389 389 * Removes a named style previously added to the document.
390 390 *
391 391 * @param nm the name of the style to remove
392 392 */
393 393 public void removeStyle(String nm) {
394 394 StyleContext styles = (StyleContext) getAttributeContext();
395 395 styles.removeStyle(nm);
396 396 }
397 397
398 398 /**
399 399 * Fetches a named style previously added.
400 400 *
401 401 * @param nm the name of the style
402 402 * @return the style
403 403 */
404 404 public Style getStyle(String nm) {
405 405 StyleContext styles = (StyleContext) getAttributeContext();
406 406 return styles.getStyle(nm);
407 407 }
408 408
409 409
410 410 /**
411 411 * Fetches the list of of style names.
412 412 *
413 413 * @return all the style names
414 414 */
415 415 public Enumeration<?> getStyleNames() {
416 416 return ((StyleContext) getAttributeContext()).getStyleNames();
417 417 }
418 418
↓ open down ↓ |
232 lines elided |
↑ open up ↑ |
419 419 /**
420 420 * Sets the logical style to use for the paragraph at the
421 421 * given position. If attributes aren't explicitly set
422 422 * for character and paragraph attributes they will resolve
423 423 * through the logical style assigned to the paragraph, which
424 424 * in turn may resolve through some hierarchy completely
425 425 * independent of the element hierarchy in the document.
426 426 * <p>
427 427 * This method is thread safe, although most Swing methods
428 428 * are not. Please see
429 - * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
429 + * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
430 430 * in Swing</A> for more information.
431 431 *
432 432 * @param pos the offset from the start of the document >= 0
433 433 * @param s the logical style to assign to the paragraph, null if none
434 434 */
435 435 public void setLogicalStyle(int pos, Style s) {
436 436 Element paragraph = getParagraphElement(pos);
437 437 if ((paragraph != null) && (paragraph instanceof AbstractElement)) {
438 438 try {
439 439 writeLock();
440 440 StyleChangeUndoableEdit edit = new StyleChangeUndoableEdit((AbstractElement)paragraph, s);
441 441 ((AbstractElement)paragraph).setResolveParent(s);
442 442 int p0 = paragraph.getStartOffset();
443 443 int p1 = paragraph.getEndOffset();
444 444 DefaultDocumentEvent e =
445 445 new DefaultDocumentEvent(p0, p1 - p0, DocumentEvent.EventType.CHANGE);
446 446 e.addEdit(edit);
447 447 e.end();
448 448 fireChangedUpdate(e);
449 449 fireUndoableEditUpdate(new UndoableEditEvent(this, e));
450 450 } finally {
451 451 writeUnlock();
452 452 }
453 453 }
454 454 }
455 455
456 456 /**
457 457 * Fetches the logical style assigned to the paragraph
458 458 * represented by the given position.
459 459 *
460 460 * @param p the location to translate to a paragraph
461 461 * and determine the logical style assigned >= 0. This
462 462 * is an offset from the start of the document.
463 463 * @return the style, null if none
464 464 */
465 465 public Style getLogicalStyle(int p) {
466 466 Style s = null;
467 467 Element paragraph = getParagraphElement(p);
468 468 if (paragraph != null) {
469 469 AttributeSet a = paragraph.getAttributes();
470 470 AttributeSet parent = a.getResolveParent();
471 471 if (parent instanceof Style) {
472 472 s = (Style) parent;
473 473 }
474 474 }
475 475 return s;
↓ open down ↓ |
36 lines elided |
↑ open up ↑ |
476 476 }
477 477
478 478 /**
479 479 * Sets attributes for some part of the document.
480 480 * A write lock is held by this operation while changes
481 481 * are being made, and a DocumentEvent is sent to the listeners
482 482 * after the change has been successfully completed.
483 483 * <p>
484 484 * This method is thread safe, although most Swing methods
485 485 * are not. Please see
486 - * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
486 + * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
487 487 * in Swing</A> for more information.
488 488 *
489 489 * @param offset the offset in the document >= 0
490 490 * @param length the length >= 0
491 491 * @param s the attributes
492 492 * @param replace true if the previous attributes should be replaced
493 493 * before setting the new attributes
494 494 */
495 495 public void setCharacterAttributes(int offset, int length, AttributeSet s, boolean replace) {
496 496 if (length == 0) {
497 497 return;
498 498 }
499 499 try {
500 500 writeLock();
501 501 DefaultDocumentEvent changes =
502 502 new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
503 503
504 504 // split elements that need it
505 505 buffer.change(offset, length, changes);
506 506
507 507 AttributeSet sCopy = s.copyAttributes();
508 508
509 509 // PENDING(prinz) - this isn't a very efficient way to iterate
510 510 int lastEnd;
511 511 for (int pos = offset; pos < (offset + length); pos = lastEnd) {
512 512 Element run = getCharacterElement(pos);
513 513 lastEnd = run.getEndOffset();
514 514 if (pos == lastEnd) {
515 515 // offset + length beyond length of document, bail.
516 516 break;
517 517 }
518 518 MutableAttributeSet attr = (MutableAttributeSet) run.getAttributes();
519 519 changes.addEdit(new AttributeUndoableEdit(run, sCopy, replace));
520 520 if (replace) {
521 521 attr.removeAttributes(attr);
522 522 }
523 523 attr.addAttributes(s);
524 524 }
525 525 changes.end();
526 526 fireChangedUpdate(changes);
527 527 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
528 528 } finally {
↓ open down ↓ |
32 lines elided |
↑ open up ↑ |
529 529 writeUnlock();
530 530 }
531 531
532 532 }
533 533
534 534 /**
535 535 * Sets attributes for a paragraph.
536 536 * <p>
537 537 * This method is thread safe, although most Swing methods
538 538 * are not. Please see
539 - * <A HREF="http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
539 + * <A HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html">Concurrency
540 540 * in Swing</A> for more information.
541 541 *
542 542 * @param offset the offset into the paragraph >= 0
543 543 * @param length the number of characters affected >= 0
544 544 * @param s the attributes
545 545 * @param replace whether to replace existing attributes, or merge them
546 546 */
547 547 public void setParagraphAttributes(int offset, int length, AttributeSet s,
548 548 boolean replace) {
549 549 try {
550 550 writeLock();
551 551 DefaultDocumentEvent changes =
552 552 new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE);
553 553
554 554 AttributeSet sCopy = s.copyAttributes();
555 555
556 556 // PENDING(prinz) - this assumes a particular element structure
557 557 Element section = getDefaultRootElement();
558 558 int index0 = section.getElementIndex(offset);
559 559 int index1 = section.getElementIndex(offset + ((length > 0) ? length - 1 : 0));
560 560 boolean isI18N = Boolean.TRUE.equals(getProperty(I18NProperty));
561 561 boolean hasRuns = false;
562 562 for (int i = index0; i <= index1; i++) {
563 563 Element paragraph = section.getElement(i);
564 564 MutableAttributeSet attr = (MutableAttributeSet) paragraph.getAttributes();
565 565 changes.addEdit(new AttributeUndoableEdit(paragraph, sCopy, replace));
566 566 if (replace) {
567 567 attr.removeAttributes(attr);
568 568 }
569 569 attr.addAttributes(s);
570 570 if (isI18N && !hasRuns) {
571 571 hasRuns = (attr.getAttribute(TextAttribute.RUN_DIRECTION) != null);
572 572 }
573 573 }
574 574
575 575 if (hasRuns) {
576 576 updateBidi( changes );
577 577 }
578 578
579 579 changes.end();
580 580 fireChangedUpdate(changes);
581 581 fireUndoableEditUpdate(new UndoableEditEvent(this, changes));
582 582 } finally {
583 583 writeUnlock();
584 584 }
585 585 }
586 586
587 587 /**
588 588 * Gets the paragraph element at the offset <code>pos</code>.
589 589 * A paragraph consists of at least one child Element, which is usually
590 590 * a leaf.
591 591 *
592 592 * @param pos the starting offset >= 0
593 593 * @return the element
594 594 */
595 595 public Element getParagraphElement(int pos) {
596 596 Element e;
597 597 for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
598 598 int index = e.getElementIndex(pos);
599 599 e = e.getElement(index);
600 600 }
601 601 if(e != null)
602 602 return e.getParentElement();
603 603 return e;
604 604 }
605 605
606 606 /**
607 607 * Gets a character element based on a position.
608 608 *
609 609 * @param pos the position in the document >= 0
610 610 * @return the element
611 611 */
612 612 public Element getCharacterElement(int pos) {
613 613 Element e;
614 614 for (e = getDefaultRootElement(); ! e.isLeaf(); ) {
615 615 int index = e.getElementIndex(pos);
616 616 e = e.getElement(index);
617 617 }
618 618 return e;
619 619 }
620 620
621 621 // --- local methods -------------------------------------------------
622 622
623 623 /**
624 624 * Updates document structure as a result of text insertion. This
625 625 * will happen within a write lock. This implementation simply
626 626 * parses the inserted content for line breaks and builds up a set
627 627 * of instructions for the element buffer.
628 628 *
629 629 * @param chng a description of the document change
630 630 * @param attr the attributes
631 631 */
632 632 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
633 633 int offset = chng.getOffset();
634 634 int length = chng.getLength();
635 635 if (attr == null) {
636 636 attr = SimpleAttributeSet.EMPTY;
637 637 }
638 638
639 639 // Paragraph attributes should come from point after insertion.
640 640 // You really only notice this when inserting at a paragraph
641 641 // boundary.
642 642 Element paragraph = getParagraphElement(offset + length);
643 643 AttributeSet pattr = paragraph.getAttributes();
644 644 // Character attributes should come from actual insertion point.
645 645 Element pParagraph = getParagraphElement(offset);
646 646 Element run = pParagraph.getElement(pParagraph.getElementIndex
647 647 (offset));
648 648 int endOffset = offset + length;
649 649 boolean insertingAtBoundry = (run.getEndOffset() == endOffset);
650 650 AttributeSet cattr = run.getAttributes();
651 651
652 652 try {
653 653 Segment s = new Segment();
654 654 Vector<ElementSpec> parseBuffer = new Vector<ElementSpec>();
655 655 ElementSpec lastStartSpec = null;
656 656 boolean insertingAfterNewline = false;
657 657 short lastStartDirection = ElementSpec.OriginateDirection;
658 658 // Check if the previous character was a newline.
659 659 if (offset > 0) {
660 660 getText(offset - 1, 1, s);
661 661 if (s.array[s.offset] == '\n') {
662 662 // Inserting after a newline.
663 663 insertingAfterNewline = true;
664 664 lastStartDirection = createSpecsForInsertAfterNewline
665 665 (paragraph, pParagraph, pattr, parseBuffer,
666 666 offset, endOffset);
667 667 for(int counter = parseBuffer.size() - 1; counter >= 0;
668 668 counter--) {
669 669 ElementSpec spec = parseBuffer.elementAt(counter);
670 670 if(spec.getType() == ElementSpec.StartTagType) {
671 671 lastStartSpec = spec;
672 672 break;
673 673 }
674 674 }
675 675 }
676 676 }
677 677 // If not inserting after a new line, pull the attributes for
678 678 // new paragraphs from the paragraph under the insertion point.
679 679 if(!insertingAfterNewline)
680 680 pattr = pParagraph.getAttributes();
681 681
682 682 getText(offset, length, s);
683 683 char[] txt = s.array;
684 684 int n = s.offset + s.count;
685 685 int lastOffset = s.offset;
686 686
687 687 for (int i = s.offset; i < n; i++) {
688 688 if (txt[i] == '\n') {
689 689 int breakOffset = i + 1;
690 690 parseBuffer.addElement(
691 691 new ElementSpec(attr, ElementSpec.ContentType,
692 692 breakOffset - lastOffset));
693 693 parseBuffer.addElement(
694 694 new ElementSpec(null, ElementSpec.EndTagType));
695 695 lastStartSpec = new ElementSpec(pattr, ElementSpec.
696 696 StartTagType);
697 697 parseBuffer.addElement(lastStartSpec);
698 698 lastOffset = breakOffset;
699 699 }
700 700 }
701 701 if (lastOffset < n) {
702 702 parseBuffer.addElement(
703 703 new ElementSpec(attr, ElementSpec.ContentType,
704 704 n - lastOffset));
705 705 }
706 706
707 707 ElementSpec first = parseBuffer.firstElement();
708 708
709 709 int docLength = getLength();
710 710
711 711 // Check for join previous of first content.
712 712 if(first.getType() == ElementSpec.ContentType &&
713 713 cattr.isEqual(attr)) {
714 714 first.setDirection(ElementSpec.JoinPreviousDirection);
715 715 }
716 716
717 717 // Do a join fracture/next for last start spec if necessary.
718 718 if(lastStartSpec != null) {
719 719 if(insertingAfterNewline) {
720 720 lastStartSpec.setDirection(lastStartDirection);
721 721 }
722 722 // Join to the fracture if NOT inserting at the end
723 723 // (fracture only happens when not inserting at end of
724 724 // paragraph).
725 725 else if(pParagraph.getEndOffset() != endOffset) {
726 726 lastStartSpec.setDirection(ElementSpec.
727 727 JoinFractureDirection);
728 728 }
729 729 // Join to next if parent of pParagraph has another
730 730 // element after pParagraph, and it isn't a leaf.
731 731 else {
732 732 Element parent = pParagraph.getParentElement();
733 733 int pParagraphIndex = parent.getElementIndex(offset);
734 734 if((pParagraphIndex + 1) < parent.getElementCount() &&
735 735 !parent.getElement(pParagraphIndex + 1).isLeaf()) {
736 736 lastStartSpec.setDirection(ElementSpec.
737 737 JoinNextDirection);
738 738 }
739 739 }
740 740 }
741 741
742 742 // Do a JoinNext for last spec if it is content, it doesn't
743 743 // already have a direction set, no new paragraphs have been
744 744 // inserted or a new paragraph has been inserted and its join
745 745 // direction isn't originate, and the element at endOffset
746 746 // is a leaf.
747 747 if(insertingAtBoundry && endOffset < docLength) {
748 748 ElementSpec last = parseBuffer.lastElement();
749 749 if(last.getType() == ElementSpec.ContentType &&
750 750 last.getDirection() != ElementSpec.JoinPreviousDirection &&
751 751 ((lastStartSpec == null && (paragraph == pParagraph ||
752 752 insertingAfterNewline)) ||
753 753 (lastStartSpec != null && lastStartSpec.getDirection() !=
754 754 ElementSpec.OriginateDirection))) {
755 755 Element nextRun = paragraph.getElement(paragraph.
756 756 getElementIndex(endOffset));
757 757 // Don't try joining to a branch!
758 758 if(nextRun.isLeaf() &&
759 759 attr.isEqual(nextRun.getAttributes())) {
760 760 last.setDirection(ElementSpec.JoinNextDirection);
761 761 }
762 762 }
763 763 }
764 764 // If not inserting at boundary and there is going to be a
765 765 // fracture, then can join next on last content if cattr
766 766 // matches the new attributes.
767 767 else if(!insertingAtBoundry && lastStartSpec != null &&
768 768 lastStartSpec.getDirection() ==
769 769 ElementSpec.JoinFractureDirection) {
770 770 ElementSpec last = parseBuffer.lastElement();
771 771 if(last.getType() == ElementSpec.ContentType &&
772 772 last.getDirection() != ElementSpec.JoinPreviousDirection &&
773 773 attr.isEqual(cattr)) {
774 774 last.setDirection(ElementSpec.JoinNextDirection);
775 775 }
776 776 }
777 777
778 778 // Check for the composed text element. If it is, merge the character attributes
779 779 // into this element as well.
780 780 if (Utilities.isComposedTextAttributeDefined(attr)) {
781 781 MutableAttributeSet mattr = (MutableAttributeSet) attr;
782 782 mattr.addAttributes(cattr);
783 783 mattr.addAttribute(AbstractDocument.ElementNameAttribute,
784 784 AbstractDocument.ContentElementName);
785 785
786 786 // Assure that the composed text element is named properly
787 787 // and doesn't have the CR attribute defined.
788 788 mattr.addAttribute(StyleConstants.NameAttribute,
789 789 AbstractDocument.ContentElementName);
790 790 if (mattr.isDefined(IMPLIED_CR)) {
791 791 mattr.removeAttribute(IMPLIED_CR);
792 792 }
793 793 }
794 794
795 795 ElementSpec[] spec = new ElementSpec[parseBuffer.size()];
796 796 parseBuffer.copyInto(spec);
797 797 buffer.insert(offset, length, spec, chng);
798 798 } catch (BadLocationException bl) {
799 799 }
800 800
801 801 super.insertUpdate( chng, attr );
802 802 }
803 803
804 804 /**
805 805 * This is called by insertUpdate when inserting after a new line.
806 806 * It generates, in <code>parseBuffer</code>, ElementSpecs that will
807 807 * position the stack in <code>paragraph</code>.<p>
808 808 * It returns the direction the last StartSpec should have (this don't
809 809 * necessarily create the last start spec).
810 810 */
811 811 short createSpecsForInsertAfterNewline(Element paragraph,
812 812 Element pParagraph, AttributeSet pattr, Vector<ElementSpec> parseBuffer,
813 813 int offset, int endOffset) {
814 814 // Need to find the common parent of pParagraph and paragraph.
815 815 if(paragraph.getParentElement() == pParagraph.getParentElement()) {
816 816 // The simple (and common) case that pParagraph and
817 817 // paragraph have the same parent.
818 818 ElementSpec spec = new ElementSpec(pattr, ElementSpec.EndTagType);
819 819 parseBuffer.addElement(spec);
820 820 spec = new ElementSpec(pattr, ElementSpec.StartTagType);
821 821 parseBuffer.addElement(spec);
822 822 if(pParagraph.getEndOffset() != endOffset)
823 823 return ElementSpec.JoinFractureDirection;
824 824
825 825 Element parent = pParagraph.getParentElement();
826 826 if((parent.getElementIndex(offset) + 1) < parent.getElementCount())
827 827 return ElementSpec.JoinNextDirection;
828 828 }
829 829 else {
830 830 // Will only happen for text with more than 2 levels.
831 831 // Find the common parent of a paragraph and pParagraph
832 832 Vector<Element> leftParents = new Vector<Element>();
833 833 Vector<Element> rightParents = new Vector<Element>();
834 834 Element e = pParagraph;
835 835 while(e != null) {
836 836 leftParents.addElement(e);
837 837 e = e.getParentElement();
838 838 }
839 839 e = paragraph;
840 840 int leftIndex = -1;
841 841 while(e != null && (leftIndex = leftParents.indexOf(e)) == -1) {
842 842 rightParents.addElement(e);
843 843 e = e.getParentElement();
844 844 }
845 845 if(e != null) {
846 846 // e identifies the common parent.
847 847 // Build the ends.
848 848 for(int counter = 0; counter < leftIndex;
849 849 counter++) {
850 850 parseBuffer.addElement(new ElementSpec
851 851 (null, ElementSpec.EndTagType));
852 852 }
853 853 // And the starts.
854 854 ElementSpec spec;
855 855 for(int counter = rightParents.size() - 1;
856 856 counter >= 0; counter--) {
857 857 spec = new ElementSpec(rightParents.elementAt(counter).getAttributes(),
858 858 ElementSpec.StartTagType);
859 859 if(counter > 0)
860 860 spec.setDirection(ElementSpec.JoinNextDirection);
861 861 parseBuffer.addElement(spec);
862 862 }
863 863 // If there are right parents, then we generated starts
864 864 // down the right subtree and there will be an element to
865 865 // join to.
866 866 if(rightParents.size() > 0)
867 867 return ElementSpec.JoinNextDirection;
868 868 // No right subtree, e.getElement(endOffset) is a
869 869 // leaf. There will be a facture.
870 870 return ElementSpec.JoinFractureDirection;
871 871 }
872 872 // else: Could throw an exception here, but should never get here!
873 873 }
874 874 return ElementSpec.OriginateDirection;
875 875 }
876 876
877 877 /**
878 878 * Updates document structure as a result of text removal.
879 879 *
880 880 * @param chng a description of the document change
881 881 */
882 882 protected void removeUpdate(DefaultDocumentEvent chng) {
883 883 super.removeUpdate(chng);
884 884 buffer.remove(chng.getOffset(), chng.getLength(), chng);
885 885 }
886 886
887 887 /**
888 888 * Creates the root element to be used to represent the
889 889 * default document structure.
890 890 *
891 891 * @return the element base
892 892 */
893 893 protected AbstractElement createDefaultRoot() {
894 894 // grabs a write-lock for this initialization and
895 895 // abandon it during initialization so in normal
896 896 // operation we can detect an illegitimate attempt
897 897 // to mutate attributes.
898 898 writeLock();
899 899 BranchElement section = new SectionElement();
900 900 BranchElement paragraph = new BranchElement(section, null);
901 901
902 902 LeafElement brk = new LeafElement(paragraph, null, 0, 1);
903 903 Element[] buff = new Element[1];
904 904 buff[0] = brk;
905 905 paragraph.replace(0, 0, buff);
906 906
907 907 buff[0] = paragraph;
908 908 section.replace(0, 0, buff);
909 909 writeUnlock();
910 910 return section;
911 911 }
912 912
913 913 /**
914 914 * Gets the foreground color from an attribute set.
915 915 *
916 916 * @param attr the attribute set
917 917 * @return the color
918 918 */
919 919 public Color getForeground(AttributeSet attr) {
920 920 StyleContext styles = (StyleContext) getAttributeContext();
921 921 return styles.getForeground(attr);
922 922 }
923 923
924 924 /**
925 925 * Gets the background color from an attribute set.
926 926 *
927 927 * @param attr the attribute set
928 928 * @return the color
929 929 */
930 930 public Color getBackground(AttributeSet attr) {
931 931 StyleContext styles = (StyleContext) getAttributeContext();
932 932 return styles.getBackground(attr);
933 933 }
934 934
935 935 /**
936 936 * Gets the font from an attribute set.
937 937 *
938 938 * @param attr the attribute set
939 939 * @return the font
940 940 */
941 941 public Font getFont(AttributeSet attr) {
942 942 StyleContext styles = (StyleContext) getAttributeContext();
943 943 return styles.getFont(attr);
944 944 }
945 945
946 946 /**
947 947 * Called when any of this document's styles have changed.
948 948 * Subclasses may wish to be intelligent about what gets damaged.
949 949 *
950 950 * @param style The Style that has changed.
951 951 */
952 952 protected void styleChanged(Style style) {
953 953 // Only propagate change updated if have content
954 954 if (getLength() != 0) {
955 955 // lazily create a ChangeUpdateRunnable
956 956 if (updateRunnable == null) {
957 957 updateRunnable = new ChangeUpdateRunnable();
958 958 }
959 959
960 960 // We may get a whole batch of these at once, so only
961 961 // queue the runnable if it is not already pending
962 962 synchronized(updateRunnable) {
963 963 if (!updateRunnable.isPending) {
964 964 SwingUtilities.invokeLater(updateRunnable);
965 965 updateRunnable.isPending = true;
966 966 }
967 967 }
968 968 }
969 969 }
970 970
971 971 /**
972 972 * Adds a document listener for notification of any changes.
973 973 *
974 974 * @param listener the listener
975 975 * @see Document#addDocumentListener
976 976 */
977 977 public void addDocumentListener(DocumentListener listener) {
978 978 synchronized(listeningStyles) {
979 979 int oldDLCount = listenerList.getListenerCount
980 980 (DocumentListener.class);
981 981 super.addDocumentListener(listener);
982 982 if (oldDLCount == 0) {
983 983 if (styleContextChangeListener == null) {
984 984 styleContextChangeListener =
985 985 createStyleContextChangeListener();
986 986 }
987 987 if (styleContextChangeListener != null) {
988 988 StyleContext styles = (StyleContext)getAttributeContext();
989 989 List<ChangeListener> staleListeners =
990 990 AbstractChangeHandler.getStaleListeners(styleContextChangeListener);
991 991 for (ChangeListener l: staleListeners) {
992 992 styles.removeChangeListener(l);
993 993 }
994 994 styles.addChangeListener(styleContextChangeListener);
995 995 }
996 996 updateStylesListeningTo();
997 997 }
998 998 }
999 999 }
1000 1000
1001 1001 /**
1002 1002 * Removes a document listener.
1003 1003 *
1004 1004 * @param listener the listener
1005 1005 * @see Document#removeDocumentListener
1006 1006 */
1007 1007 public void removeDocumentListener(DocumentListener listener) {
1008 1008 synchronized(listeningStyles) {
1009 1009 super.removeDocumentListener(listener);
1010 1010 if (listenerList.getListenerCount(DocumentListener.class) == 0) {
1011 1011 for (int counter = listeningStyles.size() - 1; counter >= 0;
1012 1012 counter--) {
1013 1013 listeningStyles.elementAt(counter).
1014 1014 removeChangeListener(styleChangeListener);
1015 1015 }
1016 1016 listeningStyles.removeAllElements();
1017 1017 if (styleContextChangeListener != null) {
1018 1018 StyleContext styles = (StyleContext)getAttributeContext();
1019 1019 styles.removeChangeListener(styleContextChangeListener);
1020 1020 }
1021 1021 }
1022 1022 }
1023 1023 }
1024 1024
1025 1025 /**
1026 1026 * Returns a new instance of StyleChangeHandler.
1027 1027 */
1028 1028 ChangeListener createStyleChangeListener() {
1029 1029 return new StyleChangeHandler(this);
1030 1030 }
1031 1031
1032 1032 /**
1033 1033 * Returns a new instance of StyleContextChangeHandler.
1034 1034 */
1035 1035 ChangeListener createStyleContextChangeListener() {
1036 1036 return new StyleContextChangeHandler(this);
1037 1037 }
1038 1038
1039 1039 /**
1040 1040 * Adds a ChangeListener to new styles, and removes ChangeListener from
1041 1041 * old styles.
1042 1042 */
1043 1043 void updateStylesListeningTo() {
1044 1044 synchronized(listeningStyles) {
1045 1045 StyleContext styles = (StyleContext)getAttributeContext();
1046 1046 if (styleChangeListener == null) {
1047 1047 styleChangeListener = createStyleChangeListener();
1048 1048 }
1049 1049 if (styleChangeListener != null && styles != null) {
1050 1050 Enumeration styleNames = styles.getStyleNames();
1051 1051 Vector v = (Vector)listeningStyles.clone();
1052 1052 listeningStyles.removeAllElements();
1053 1053 List<ChangeListener> staleListeners =
1054 1054 AbstractChangeHandler.getStaleListeners(styleChangeListener);
1055 1055 while (styleNames.hasMoreElements()) {
1056 1056 String name = (String)styleNames.nextElement();
1057 1057 Style aStyle = styles.getStyle(name);
1058 1058 int index = v.indexOf(aStyle);
1059 1059 listeningStyles.addElement(aStyle);
1060 1060 if (index == -1) {
1061 1061 for (ChangeListener l: staleListeners) {
1062 1062 aStyle.removeChangeListener(l);
1063 1063 }
1064 1064 aStyle.addChangeListener(styleChangeListener);
1065 1065 }
1066 1066 else {
1067 1067 v.removeElementAt(index);
1068 1068 }
1069 1069 }
1070 1070 for (int counter = v.size() - 1; counter >= 0; counter--) {
1071 1071 Style aStyle = (Style)v.elementAt(counter);
1072 1072 aStyle.removeChangeListener(styleChangeListener);
1073 1073 }
1074 1074 if (listeningStyles.size() == 0) {
1075 1075 styleChangeListener = null;
1076 1076 }
1077 1077 }
1078 1078 }
1079 1079 }
1080 1080
1081 1081 private void readObject(ObjectInputStream s)
1082 1082 throws ClassNotFoundException, IOException {
1083 1083 listeningStyles = new Vector<Style>();
1084 1084 s.defaultReadObject();
1085 1085 // Reinstall style listeners.
1086 1086 if (styleContextChangeListener == null &&
1087 1087 listenerList.getListenerCount(DocumentListener.class) > 0) {
1088 1088 styleContextChangeListener = createStyleContextChangeListener();
1089 1089 if (styleContextChangeListener != null) {
1090 1090 StyleContext styles = (StyleContext)getAttributeContext();
1091 1091 styles.addChangeListener(styleContextChangeListener);
1092 1092 }
1093 1093 updateStylesListeningTo();
1094 1094 }
1095 1095 }
1096 1096
1097 1097 // --- member variables -----------------------------------------------------------
1098 1098
1099 1099 /**
1100 1100 * The default size of the initial content buffer.
1101 1101 */
1102 1102 public static final int BUFFER_SIZE_DEFAULT = 4096;
1103 1103
1104 1104 protected ElementBuffer buffer;
1105 1105
1106 1106 /** Styles listening to. */
1107 1107 private transient Vector<Style> listeningStyles;
1108 1108
1109 1109 /** Listens to Styles. */
1110 1110 private transient ChangeListener styleChangeListener;
1111 1111
1112 1112 /** Listens to Styles. */
1113 1113 private transient ChangeListener styleContextChangeListener;
1114 1114
1115 1115 /** Run to create a change event for the document */
1116 1116 private transient ChangeUpdateRunnable updateRunnable;
1117 1117
1118 1118 /**
1119 1119 * Default root element for a document... maps out the
1120 1120 * paragraphs/lines contained.
1121 1121 * <p>
1122 1122 * <strong>Warning:</strong>
1123 1123 * Serialized objects of this class will not be compatible with
1124 1124 * future Swing releases. The current serialization support is
1125 1125 * appropriate for short term storage or RMI between applications running
1126 1126 * the same version of Swing. As of 1.4, support for long term storage
1127 1127 * of all JavaBeans™
1128 1128 * has been added to the <code>java.beans</code> package.
1129 1129 * Please see {@link java.beans.XMLEncoder}.
1130 1130 */
1131 1131 protected class SectionElement extends BranchElement {
1132 1132
1133 1133 /**
1134 1134 * Creates a new SectionElement.
1135 1135 */
1136 1136 public SectionElement() {
1137 1137 super(null, null);
1138 1138 }
1139 1139
1140 1140 /**
1141 1141 * Gets the name of the element.
1142 1142 *
1143 1143 * @return the name
1144 1144 */
1145 1145 public String getName() {
1146 1146 return SectionElementName;
1147 1147 }
1148 1148 }
1149 1149
1150 1150 /**
1151 1151 * Specification for building elements.
1152 1152 * <p>
1153 1153 * <strong>Warning:</strong>
1154 1154 * Serialized objects of this class will not be compatible with
1155 1155 * future Swing releases. The current serialization support is
1156 1156 * appropriate for short term storage or RMI between applications running
1157 1157 * the same version of Swing. As of 1.4, support for long term storage
1158 1158 * of all JavaBeans™
1159 1159 * has been added to the <code>java.beans</code> package.
1160 1160 * Please see {@link java.beans.XMLEncoder}.
1161 1161 */
1162 1162 public static class ElementSpec {
1163 1163
1164 1164 /**
1165 1165 * A possible value for getType. This specifies
1166 1166 * that this record type is a start tag and
1167 1167 * represents markup that specifies the start
1168 1168 * of an element.
1169 1169 */
1170 1170 public static final short StartTagType = 1;
1171 1171
1172 1172 /**
1173 1173 * A possible value for getType. This specifies
1174 1174 * that this record type is a end tag and
1175 1175 * represents markup that specifies the end
1176 1176 * of an element.
1177 1177 */
1178 1178 public static final short EndTagType = 2;
1179 1179
1180 1180 /**
1181 1181 * A possible value for getType. This specifies
1182 1182 * that this record type represents content.
1183 1183 */
1184 1184 public static final short ContentType = 3;
1185 1185
1186 1186 /**
1187 1187 * A possible value for getDirection. This specifies
1188 1188 * that the data associated with this record should
1189 1189 * be joined to what precedes it.
1190 1190 */
1191 1191 public static final short JoinPreviousDirection = 4;
1192 1192
1193 1193 /**
1194 1194 * A possible value for getDirection. This specifies
1195 1195 * that the data associated with this record should
1196 1196 * be joined to what follows it.
1197 1197 */
1198 1198 public static final short JoinNextDirection = 5;
1199 1199
1200 1200 /**
1201 1201 * A possible value for getDirection. This specifies
1202 1202 * that the data associated with this record should
1203 1203 * be used to originate a new element. This would be
1204 1204 * the normal value.
1205 1205 */
1206 1206 public static final short OriginateDirection = 6;
1207 1207
1208 1208 /**
1209 1209 * A possible value for getDirection. This specifies
1210 1210 * that the data associated with this record should
1211 1211 * be joined to the fractured element.
1212 1212 */
1213 1213 public static final short JoinFractureDirection = 7;
1214 1214
1215 1215
1216 1216 /**
1217 1217 * Constructor useful for markup when the markup will not
1218 1218 * be stored in the document.
1219 1219 *
1220 1220 * @param a the attributes for the element
1221 1221 * @param type the type of the element (StartTagType, EndTagType,
1222 1222 * ContentType)
1223 1223 */
1224 1224 public ElementSpec(AttributeSet a, short type) {
1225 1225 this(a, type, null, 0, 0);
1226 1226 }
1227 1227
1228 1228 /**
1229 1229 * Constructor for parsing inside the document when
1230 1230 * the data has already been added, but len information
1231 1231 * is needed.
1232 1232 *
1233 1233 * @param a the attributes for the element
1234 1234 * @param type the type of the element (StartTagType, EndTagType,
1235 1235 * ContentType)
1236 1236 * @param len the length >= 0
1237 1237 */
1238 1238 public ElementSpec(AttributeSet a, short type, int len) {
1239 1239 this(a, type, null, 0, len);
1240 1240 }
1241 1241
1242 1242 /**
1243 1243 * Constructor for creating a spec externally for batch
1244 1244 * input of content and markup into the document.
1245 1245 *
1246 1246 * @param a the attributes for the element
1247 1247 * @param type the type of the element (StartTagType, EndTagType,
1248 1248 * ContentType)
1249 1249 * @param txt the text for the element
1250 1250 * @param offs the offset into the text >= 0
1251 1251 * @param len the length of the text >= 0
1252 1252 */
1253 1253 public ElementSpec(AttributeSet a, short type, char[] txt,
1254 1254 int offs, int len) {
1255 1255 attr = a;
1256 1256 this.type = type;
1257 1257 this.data = txt;
1258 1258 this.offs = offs;
1259 1259 this.len = len;
1260 1260 this.direction = OriginateDirection;
1261 1261 }
1262 1262
1263 1263 /**
1264 1264 * Sets the element type.
1265 1265 *
1266 1266 * @param type the type of the element (StartTagType, EndTagType,
1267 1267 * ContentType)
1268 1268 */
1269 1269 public void setType(short type) {
1270 1270 this.type = type;
1271 1271 }
1272 1272
1273 1273 /**
1274 1274 * Gets the element type.
1275 1275 *
1276 1276 * @return the type of the element (StartTagType, EndTagType,
1277 1277 * ContentType)
1278 1278 */
1279 1279 public short getType() {
1280 1280 return type;
1281 1281 }
1282 1282
1283 1283 /**
1284 1284 * Sets the direction.
1285 1285 *
1286 1286 * @param direction the direction (JoinPreviousDirection,
1287 1287 * JoinNextDirection)
1288 1288 */
1289 1289 public void setDirection(short direction) {
1290 1290 this.direction = direction;
1291 1291 }
1292 1292
1293 1293 /**
1294 1294 * Gets the direction.
1295 1295 *
1296 1296 * @return the direction (JoinPreviousDirection, JoinNextDirection)
1297 1297 */
1298 1298 public short getDirection() {
1299 1299 return direction;
1300 1300 }
1301 1301
1302 1302 /**
1303 1303 * Gets the element attributes.
1304 1304 *
1305 1305 * @return the attribute set
1306 1306 */
1307 1307 public AttributeSet getAttributes() {
1308 1308 return attr;
1309 1309 }
1310 1310
1311 1311 /**
1312 1312 * Gets the array of characters.
1313 1313 *
1314 1314 * @return the array
1315 1315 */
1316 1316 public char[] getArray() {
1317 1317 return data;
1318 1318 }
1319 1319
1320 1320
1321 1321 /**
1322 1322 * Gets the starting offset.
1323 1323 *
1324 1324 * @return the offset >= 0
1325 1325 */
1326 1326 public int getOffset() {
1327 1327 return offs;
1328 1328 }
1329 1329
1330 1330 /**
1331 1331 * Gets the length.
1332 1332 *
1333 1333 * @return the length >= 0
1334 1334 */
1335 1335 public int getLength() {
1336 1336 return len;
1337 1337 }
1338 1338
1339 1339 /**
1340 1340 * Converts the element to a string.
1341 1341 *
1342 1342 * @return the string
1343 1343 */
1344 1344 public String toString() {
1345 1345 String tlbl = "??";
1346 1346 String plbl = "??";
1347 1347 switch(type) {
1348 1348 case StartTagType:
1349 1349 tlbl = "StartTag";
1350 1350 break;
1351 1351 case ContentType:
1352 1352 tlbl = "Content";
1353 1353 break;
1354 1354 case EndTagType:
1355 1355 tlbl = "EndTag";
1356 1356 break;
1357 1357 }
1358 1358 switch(direction) {
1359 1359 case JoinPreviousDirection:
1360 1360 plbl = "JoinPrevious";
1361 1361 break;
1362 1362 case JoinNextDirection:
1363 1363 plbl = "JoinNext";
1364 1364 break;
1365 1365 case OriginateDirection:
1366 1366 plbl = "Originate";
1367 1367 break;
1368 1368 case JoinFractureDirection:
1369 1369 plbl = "Fracture";
1370 1370 break;
1371 1371 }
1372 1372 return tlbl + ":" + plbl + ":" + getLength();
1373 1373 }
1374 1374
1375 1375 private AttributeSet attr;
1376 1376 private int len;
1377 1377 private short type;
1378 1378 private short direction;
1379 1379
1380 1380 private int offs;
1381 1381 private char[] data;
1382 1382 }
1383 1383
1384 1384 /**
1385 1385 * Class to manage changes to the element
1386 1386 * hierarchy.
1387 1387 * <p>
1388 1388 * <strong>Warning:</strong>
1389 1389 * Serialized objects of this class will not be compatible with
1390 1390 * future Swing releases. The current serialization support is
1391 1391 * appropriate for short term storage or RMI between applications running
1392 1392 * the same version of Swing. As of 1.4, support for long term storage
1393 1393 * of all JavaBeans™
1394 1394 * has been added to the <code>java.beans</code> package.
1395 1395 * Please see {@link java.beans.XMLEncoder}.
1396 1396 */
1397 1397 public class ElementBuffer implements Serializable {
1398 1398
1399 1399 /**
1400 1400 * Creates a new ElementBuffer.
1401 1401 *
1402 1402 * @param root the root element
1403 1403 * @since 1.4
1404 1404 */
1405 1405 public ElementBuffer(Element root) {
1406 1406 this.root = root;
1407 1407 changes = new Vector<ElemChanges>();
1408 1408 path = new Stack<ElemChanges>();
1409 1409 }
1410 1410
1411 1411 /**
1412 1412 * Gets the root element.
1413 1413 *
1414 1414 * @return the root element
1415 1415 */
1416 1416 public Element getRootElement() {
1417 1417 return root;
1418 1418 }
1419 1419
1420 1420 /**
1421 1421 * Inserts new content.
1422 1422 *
1423 1423 * @param offset the starting offset >= 0
1424 1424 * @param length the length >= 0
1425 1425 * @param data the data to insert
1426 1426 * @param de the event capturing this edit
1427 1427 */
1428 1428 public void insert(int offset, int length, ElementSpec[] data,
1429 1429 DefaultDocumentEvent de) {
1430 1430 if (length == 0) {
1431 1431 // Nothing was inserted, no structure change.
1432 1432 return;
1433 1433 }
1434 1434 insertOp = true;
1435 1435 beginEdits(offset, length);
1436 1436 insertUpdate(data);
1437 1437 endEdits(de);
1438 1438
1439 1439 insertOp = false;
1440 1440 }
1441 1441
1442 1442 void create(int length, ElementSpec[] data, DefaultDocumentEvent de) {
1443 1443 insertOp = true;
1444 1444 beginEdits(offset, length);
1445 1445
1446 1446 // PENDING(prinz) this needs to be fixed to create a new
1447 1447 // root element as well, but requires changes to the
1448 1448 // DocumentEvent to inform the views that there is a new
1449 1449 // root element.
1450 1450
1451 1451 // Recreate the ending fake element to have the correct offsets.
1452 1452 Element elem = root;
1453 1453 int index = elem.getElementIndex(0);
1454 1454 while (! elem.isLeaf()) {
1455 1455 Element child = elem.getElement(index);
1456 1456 push(elem, index);
1457 1457 elem = child;
1458 1458 index = elem.getElementIndex(0);
1459 1459 }
1460 1460 ElemChanges ec = path.peek();
1461 1461 Element child = ec.parent.getElement(ec.index);
1462 1462 ec.added.addElement(createLeafElement(ec.parent,
1463 1463 child.getAttributes(), getLength(),
1464 1464 child.getEndOffset()));
1465 1465 ec.removed.addElement(child);
1466 1466 while (path.size() > 1) {
1467 1467 pop();
1468 1468 }
1469 1469
1470 1470 int n = data.length;
1471 1471
1472 1472 // Reset the root elements attributes.
1473 1473 AttributeSet newAttrs = null;
1474 1474 if (n > 0 && data[0].getType() == ElementSpec.StartTagType) {
1475 1475 newAttrs = data[0].getAttributes();
1476 1476 }
1477 1477 if (newAttrs == null) {
1478 1478 newAttrs = SimpleAttributeSet.EMPTY;
1479 1479 }
1480 1480 MutableAttributeSet attr = (MutableAttributeSet)root.
1481 1481 getAttributes();
1482 1482 de.addEdit(new AttributeUndoableEdit(root, newAttrs, true));
1483 1483 attr.removeAttributes(attr);
1484 1484 attr.addAttributes(newAttrs);
1485 1485
1486 1486 // fold in the specified subtree
1487 1487 for (int i = 1; i < n; i++) {
1488 1488 insertElement(data[i]);
1489 1489 }
1490 1490
1491 1491 // pop the remaining path
1492 1492 while (path.size() != 0) {
1493 1493 pop();
1494 1494 }
1495 1495
1496 1496 endEdits(de);
1497 1497 insertOp = false;
1498 1498 }
1499 1499
1500 1500 /**
1501 1501 * Removes content.
1502 1502 *
1503 1503 * @param offset the starting offset >= 0
1504 1504 * @param length the length >= 0
1505 1505 * @param de the event capturing this edit
1506 1506 */
1507 1507 public void remove(int offset, int length, DefaultDocumentEvent de) {
1508 1508 beginEdits(offset, length);
1509 1509 removeUpdate();
1510 1510 endEdits(de);
1511 1511 }
1512 1512
1513 1513 /**
1514 1514 * Changes content.
1515 1515 *
1516 1516 * @param offset the starting offset >= 0
1517 1517 * @param length the length >= 0
1518 1518 * @param de the event capturing this edit
1519 1519 */
1520 1520 public void change(int offset, int length, DefaultDocumentEvent de) {
1521 1521 beginEdits(offset, length);
1522 1522 changeUpdate();
1523 1523 endEdits(de);
1524 1524 }
1525 1525
1526 1526 /**
1527 1527 * Inserts an update into the document.
1528 1528 *
1529 1529 * @param data the elements to insert
1530 1530 */
1531 1531 protected void insertUpdate(ElementSpec[] data) {
1532 1532 // push the path
1533 1533 Element elem = root;
1534 1534 int index = elem.getElementIndex(offset);
1535 1535 while (! elem.isLeaf()) {
1536 1536 Element child = elem.getElement(index);
1537 1537 push(elem, (child.isLeaf() ? index : index+1));
1538 1538 elem = child;
1539 1539 index = elem.getElementIndex(offset);
1540 1540 }
1541 1541
1542 1542 // Build a copy of the original path.
1543 1543 insertPath = new ElemChanges[path.size()];
1544 1544 path.copyInto(insertPath);
1545 1545
1546 1546 // Haven't created the fracture yet.
1547 1547 createdFracture = false;
1548 1548
1549 1549 // Insert the first content.
1550 1550 int i;
1551 1551
1552 1552 recreateLeafs = false;
1553 1553 if(data[0].getType() == ElementSpec.ContentType) {
1554 1554 insertFirstContent(data);
1555 1555 pos += data[0].getLength();
1556 1556 i = 1;
1557 1557 }
1558 1558 else {
1559 1559 fractureDeepestLeaf(data);
1560 1560 i = 0;
1561 1561 }
1562 1562
1563 1563 // fold in the specified subtree
1564 1564 int n = data.length;
1565 1565 for (; i < n; i++) {
1566 1566 insertElement(data[i]);
1567 1567 }
1568 1568
1569 1569 // Fracture, if we haven't yet.
1570 1570 if(!createdFracture)
1571 1571 fracture(-1);
1572 1572
1573 1573 // pop the remaining path
1574 1574 while (path.size() != 0) {
1575 1575 pop();
1576 1576 }
1577 1577
1578 1578 // Offset the last index if necessary.
1579 1579 if(offsetLastIndex && offsetLastIndexOnReplace) {
1580 1580 insertPath[insertPath.length - 1].index++;
1581 1581 }
1582 1582
1583 1583 // Make sure an edit is going to be created for each of the
1584 1584 // original path items that have a change.
1585 1585 for(int counter = insertPath.length - 1; counter >= 0;
1586 1586 counter--) {
1587 1587 ElemChanges change = insertPath[counter];
1588 1588 if(change.parent == fracturedParent)
1589 1589 change.added.addElement(fracturedChild);
1590 1590 if((change.added.size() > 0 ||
1591 1591 change.removed.size() > 0) && !changes.contains(change)) {
1592 1592 // PENDING(sky): Do I need to worry about order here?
1593 1593 changes.addElement(change);
1594 1594 }
1595 1595 }
1596 1596
1597 1597 // An insert at 0 with an initial end implies some elements
1598 1598 // will have no children (the bottomost leaf would have length 0)
1599 1599 // this will find what element need to be removed and remove it.
1600 1600 if (offset == 0 && fracturedParent != null &&
1601 1601 data[0].getType() == ElementSpec.EndTagType) {
1602 1602 int counter = 0;
1603 1603 while (counter < data.length &&
1604 1604 data[counter].getType() == ElementSpec.EndTagType) {
1605 1605 counter++;
1606 1606 }
1607 1607 ElemChanges change = insertPath[insertPath.length -
1608 1608 counter - 1];
1609 1609 change.removed.insertElementAt(change.parent.getElement
1610 1610 (--change.index), 0);
1611 1611 }
1612 1612 }
1613 1613
1614 1614 /**
1615 1615 * Updates the element structure in response to a removal from the
1616 1616 * associated sequence in the document. Any elements consumed by the
1617 1617 * span of the removal are removed.
1618 1618 */
1619 1619 protected void removeUpdate() {
1620 1620 removeElements(root, offset, offset + length);
1621 1621 }
1622 1622
1623 1623 /**
1624 1624 * Updates the element structure in response to a change in the
1625 1625 * document.
1626 1626 */
1627 1627 protected void changeUpdate() {
1628 1628 boolean didEnd = split(offset, length);
1629 1629 if (! didEnd) {
1630 1630 // need to do the other end
1631 1631 while (path.size() != 0) {
1632 1632 pop();
1633 1633 }
1634 1634 split(offset + length, 0);
1635 1635 }
1636 1636 while (path.size() != 0) {
1637 1637 pop();
1638 1638 }
1639 1639 }
1640 1640
1641 1641 boolean split(int offs, int len) {
1642 1642 boolean splitEnd = false;
1643 1643 // push the path
1644 1644 Element e = root;
1645 1645 int index = e.getElementIndex(offs);
1646 1646 while (! e.isLeaf()) {
1647 1647 push(e, index);
1648 1648 e = e.getElement(index);
1649 1649 index = e.getElementIndex(offs);
1650 1650 }
1651 1651
1652 1652 ElemChanges ec = path.peek();
1653 1653 Element child = ec.parent.getElement(ec.index);
1654 1654 // make sure there is something to do... if the
1655 1655 // offset is already at a boundary then there is
1656 1656 // nothing to do.
1657 1657 if (child.getStartOffset() < offs && offs < child.getEndOffset()) {
1658 1658 // we need to split, now see if the other end is within
1659 1659 // the same parent.
1660 1660 int index0 = ec.index;
1661 1661 int index1 = index0;
1662 1662 if (((offs + len) < ec.parent.getEndOffset()) && (len != 0)) {
1663 1663 // it's a range split in the same parent
1664 1664 index1 = ec.parent.getElementIndex(offs+len);
1665 1665 if (index1 == index0) {
1666 1666 // it's a three-way split
1667 1667 ec.removed.addElement(child);
1668 1668 e = createLeafElement(ec.parent, child.getAttributes(),
1669 1669 child.getStartOffset(), offs);
1670 1670 ec.added.addElement(e);
1671 1671 e = createLeafElement(ec.parent, child.getAttributes(),
1672 1672 offs, offs + len);
1673 1673 ec.added.addElement(e);
1674 1674 e = createLeafElement(ec.parent, child.getAttributes(),
1675 1675 offs + len, child.getEndOffset());
1676 1676 ec.added.addElement(e);
1677 1677 return true;
1678 1678 } else {
1679 1679 child = ec.parent.getElement(index1);
1680 1680 if ((offs + len) == child.getStartOffset()) {
1681 1681 // end is already on a boundary
1682 1682 index1 = index0;
1683 1683 }
1684 1684 }
1685 1685 splitEnd = true;
1686 1686 }
1687 1687
1688 1688 // split the first location
1689 1689 pos = offs;
1690 1690 child = ec.parent.getElement(index0);
1691 1691 ec.removed.addElement(child);
1692 1692 e = createLeafElement(ec.parent, child.getAttributes(),
1693 1693 child.getStartOffset(), pos);
1694 1694 ec.added.addElement(e);
1695 1695 e = createLeafElement(ec.parent, child.getAttributes(),
1696 1696 pos, child.getEndOffset());
1697 1697 ec.added.addElement(e);
1698 1698
1699 1699 // pick up things in the middle
1700 1700 for (int i = index0 + 1; i < index1; i++) {
1701 1701 child = ec.parent.getElement(i);
1702 1702 ec.removed.addElement(child);
1703 1703 ec.added.addElement(child);
1704 1704 }
1705 1705
1706 1706 if (index1 != index0) {
1707 1707 child = ec.parent.getElement(index1);
1708 1708 pos = offs + len;
1709 1709 ec.removed.addElement(child);
1710 1710 e = createLeafElement(ec.parent, child.getAttributes(),
1711 1711 child.getStartOffset(), pos);
1712 1712 ec.added.addElement(e);
1713 1713 e = createLeafElement(ec.parent, child.getAttributes(),
1714 1714 pos, child.getEndOffset());
1715 1715 ec.added.addElement(e);
1716 1716 }
1717 1717 }
1718 1718 return splitEnd;
1719 1719 }
1720 1720
1721 1721 /**
1722 1722 * Creates the UndoableEdit record for the edits made
1723 1723 * in the buffer.
1724 1724 */
1725 1725 void endEdits(DefaultDocumentEvent de) {
1726 1726 int n = changes.size();
1727 1727 for (int i = 0; i < n; i++) {
1728 1728 ElemChanges ec = changes.elementAt(i);
1729 1729 Element[] removed = new Element[ec.removed.size()];
1730 1730 ec.removed.copyInto(removed);
1731 1731 Element[] added = new Element[ec.added.size()];
1732 1732 ec.added.copyInto(added);
1733 1733 int index = ec.index;
1734 1734 ((BranchElement) ec.parent).replace(index, removed.length, added);
1735 1735 ElementEdit ee = new ElementEdit(ec.parent, index, removed, added);
1736 1736 de.addEdit(ee);
1737 1737 }
1738 1738
1739 1739 changes.removeAllElements();
1740 1740 path.removeAllElements();
1741 1741
1742 1742 /*
1743 1743 for (int i = 0; i < n; i++) {
1744 1744 ElemChanges ec = (ElemChanges) changes.elementAt(i);
1745 1745 System.err.print("edited: " + ec.parent + " at: " + ec.index +
1746 1746 " removed " + ec.removed.size());
1747 1747 if (ec.removed.size() > 0) {
1748 1748 int r0 = ((Element) ec.removed.firstElement()).getStartOffset();
1749 1749 int r1 = ((Element) ec.removed.lastElement()).getEndOffset();
1750 1750 System.err.print("[" + r0 + "," + r1 + "]");
1751 1751 }
1752 1752 System.err.print(" added " + ec.added.size());
1753 1753 if (ec.added.size() > 0) {
1754 1754 int p0 = ((Element) ec.added.firstElement()).getStartOffset();
1755 1755 int p1 = ((Element) ec.added.lastElement()).getEndOffset();
1756 1756 System.err.print("[" + p0 + "," + p1 + "]");
1757 1757 }
1758 1758 System.err.println("");
1759 1759 }
1760 1760 */
1761 1761 }
1762 1762
1763 1763 /**
1764 1764 * Initialize the buffer
1765 1765 */
1766 1766 void beginEdits(int offset, int length) {
1767 1767 this.offset = offset;
1768 1768 this.length = length;
1769 1769 this.endOffset = offset + length;
1770 1770 pos = offset;
1771 1771 if (changes == null) {
1772 1772 changes = new Vector<ElemChanges>();
1773 1773 } else {
1774 1774 changes.removeAllElements();
1775 1775 }
1776 1776 if (path == null) {
1777 1777 path = new Stack<ElemChanges>();
1778 1778 } else {
1779 1779 path.removeAllElements();
1780 1780 }
1781 1781 fracturedParent = null;
1782 1782 fracturedChild = null;
1783 1783 offsetLastIndex = offsetLastIndexOnReplace = false;
1784 1784 }
1785 1785
1786 1786 /**
1787 1787 * Pushes a new element onto the stack that represents
1788 1788 * the current path.
1789 1789 * @param record Whether or not the push should be
1790 1790 * recorded as an element change or not.
1791 1791 * @param isFracture true if pushing on an element that was created
1792 1792 * as the result of a fracture.
1793 1793 */
1794 1794 void push(Element e, int index, boolean isFracture) {
1795 1795 ElemChanges ec = new ElemChanges(e, index, isFracture);
1796 1796 path.push(ec);
1797 1797 }
1798 1798
1799 1799 void push(Element e, int index) {
1800 1800 push(e, index, false);
1801 1801 }
1802 1802
1803 1803 void pop() {
1804 1804 ElemChanges ec = path.peek();
1805 1805 path.pop();
1806 1806 if ((ec.added.size() > 0) || (ec.removed.size() > 0)) {
1807 1807 changes.addElement(ec);
1808 1808 } else if (! path.isEmpty()) {
1809 1809 Element e = ec.parent;
1810 1810 if(e.getElementCount() == 0) {
1811 1811 // if we pushed a branch element that didn't get
1812 1812 // used, make sure its not marked as having been added.
1813 1813 ec = path.peek();
1814 1814 ec.added.removeElement(e);
1815 1815 }
1816 1816 }
1817 1817 }
1818 1818
1819 1819 /**
1820 1820 * move the current offset forward by n.
1821 1821 */
1822 1822 void advance(int n) {
1823 1823 pos += n;
1824 1824 }
1825 1825
1826 1826 void insertElement(ElementSpec es) {
1827 1827 ElemChanges ec = path.peek();
1828 1828 switch(es.getType()) {
1829 1829 case ElementSpec.StartTagType:
1830 1830 switch(es.getDirection()) {
1831 1831 case ElementSpec.JoinNextDirection:
1832 1832 // Don't create a new element, use the existing one
1833 1833 // at the specified location.
1834 1834 Element parent = ec.parent.getElement(ec.index);
1835 1835
1836 1836 if(parent.isLeaf()) {
1837 1837 // This happens if inserting into a leaf, followed
1838 1838 // by a join next where next sibling is not a leaf.
1839 1839 if((ec.index + 1) < ec.parent.getElementCount())
1840 1840 parent = ec.parent.getElement(ec.index + 1);
1841 1841 else
1842 1842 throw new StateInvariantError("Join next to leaf");
1843 1843 }
1844 1844 // Not really a fracture, but need to treat it like
1845 1845 // one so that content join next will work correctly.
1846 1846 // We can do this because there will never be a join
1847 1847 // next followed by a join fracture.
1848 1848 push(parent, 0, true);
1849 1849 break;
1850 1850 case ElementSpec.JoinFractureDirection:
1851 1851 if(!createdFracture) {
1852 1852 // Should always be something on the stack!
1853 1853 fracture(path.size() - 1);
1854 1854 }
1855 1855 // If parent isn't a fracture, fracture will be
1856 1856 // fracturedChild.
1857 1857 if(!ec.isFracture) {
1858 1858 push(fracturedChild, 0, true);
1859 1859 }
1860 1860 else
1861 1861 // Parent is a fracture, use 1st element.
1862 1862 push(ec.parent.getElement(0), 0, true);
1863 1863 break;
1864 1864 default:
1865 1865 Element belem = createBranchElement(ec.parent,
1866 1866 es.getAttributes());
1867 1867 ec.added.addElement(belem);
1868 1868 push(belem, 0);
1869 1869 break;
1870 1870 }
1871 1871 break;
1872 1872 case ElementSpec.EndTagType:
1873 1873 pop();
1874 1874 break;
1875 1875 case ElementSpec.ContentType:
1876 1876 int len = es.getLength();
1877 1877 if (es.getDirection() != ElementSpec.JoinNextDirection) {
1878 1878 Element leaf = createLeafElement(ec.parent, es.getAttributes(),
1879 1879 pos, pos + len);
1880 1880 ec.added.addElement(leaf);
1881 1881 }
1882 1882 else {
1883 1883 // JoinNext on tail is only applicable if last element
1884 1884 // and attributes come from that of first element.
1885 1885 // With a little extra testing it would be possible
1886 1886 // to NOT due this again, as more than likely fracture()
1887 1887 // created this element.
1888 1888 if(!ec.isFracture) {
1889 1889 Element first = null;
1890 1890 if(insertPath != null) {
1891 1891 for(int counter = insertPath.length - 1;
1892 1892 counter >= 0; counter--) {
1893 1893 if(insertPath[counter] == ec) {
1894 1894 if(counter != (insertPath.length - 1))
1895 1895 first = ec.parent.getElement(ec.index);
1896 1896 break;
1897 1897 }
1898 1898 }
1899 1899 }
1900 1900 if(first == null)
1901 1901 first = ec.parent.getElement(ec.index + 1);
1902 1902 Element leaf = createLeafElement(ec.parent, first.
1903 1903 getAttributes(), pos, first.getEndOffset());
1904 1904 ec.added.addElement(leaf);
1905 1905 ec.removed.addElement(first);
1906 1906 }
1907 1907 else {
1908 1908 // Parent was fractured element.
1909 1909 Element first = ec.parent.getElement(0);
1910 1910 Element leaf = createLeafElement(ec.parent, first.
1911 1911 getAttributes(), pos, first.getEndOffset());
1912 1912 ec.added.addElement(leaf);
1913 1913 ec.removed.addElement(first);
1914 1914 }
1915 1915 }
1916 1916 pos += len;
1917 1917 break;
1918 1918 }
1919 1919 }
1920 1920
1921 1921 /**
1922 1922 * Remove the elements from <code>elem</code> in range
1923 1923 * <code>rmOffs0</code>, <code>rmOffs1</code>. This uses
1924 1924 * <code>canJoin</code> and <code>join</code> to handle joining
1925 1925 * the endpoints of the insertion.
1926 1926 *
1927 1927 * @return true if elem will no longer have any elements.
1928 1928 */
1929 1929 boolean removeElements(Element elem, int rmOffs0, int rmOffs1) {
1930 1930 if (! elem.isLeaf()) {
1931 1931 // update path for changes
1932 1932 int index0 = elem.getElementIndex(rmOffs0);
1933 1933 int index1 = elem.getElementIndex(rmOffs1);
1934 1934 push(elem, index0);
1935 1935 ElemChanges ec = path.peek();
1936 1936
1937 1937 // if the range is contained by one element,
1938 1938 // we just forward the request
1939 1939 if (index0 == index1) {
1940 1940 Element child0 = elem.getElement(index0);
1941 1941 if(rmOffs0 <= child0.getStartOffset() &&
1942 1942 rmOffs1 >= child0.getEndOffset()) {
1943 1943 // Element totally removed.
1944 1944 ec.removed.addElement(child0);
1945 1945 }
1946 1946 else if(removeElements(child0, rmOffs0, rmOffs1)) {
1947 1947 ec.removed.addElement(child0);
1948 1948 }
1949 1949 } else {
1950 1950 // the removal range spans elements. If we can join
1951 1951 // the two endpoints, do it. Otherwise we remove the
1952 1952 // interior and forward to the endpoints.
1953 1953 Element child0 = elem.getElement(index0);
1954 1954 Element child1 = elem.getElement(index1);
1955 1955 boolean containsOffs1 = (rmOffs1 < elem.getEndOffset());
1956 1956 if (containsOffs1 && canJoin(child0, child1)) {
1957 1957 // remove and join
1958 1958 for (int i = index0; i <= index1; i++) {
1959 1959 ec.removed.addElement(elem.getElement(i));
1960 1960 }
1961 1961 Element e = join(elem, child0, child1, rmOffs0, rmOffs1);
1962 1962 ec.added.addElement(e);
1963 1963 } else {
1964 1964 // remove interior and forward
1965 1965 int rmIndex0 = index0 + 1;
1966 1966 int rmIndex1 = index1 - 1;
1967 1967 if (child0.getStartOffset() == rmOffs0 ||
1968 1968 (index0 == 0 &&
1969 1969 child0.getStartOffset() > rmOffs0 &&
1970 1970 child0.getEndOffset() <= rmOffs1)) {
1971 1971 // start element completely consumed
1972 1972 child0 = null;
1973 1973 rmIndex0 = index0;
1974 1974 }
1975 1975 if (!containsOffs1) {
1976 1976 child1 = null;
1977 1977 rmIndex1++;
1978 1978 }
1979 1979 else if (child1.getStartOffset() == rmOffs1) {
1980 1980 // end element not touched
1981 1981 child1 = null;
1982 1982 }
1983 1983 if (rmIndex0 <= rmIndex1) {
1984 1984 ec.index = rmIndex0;
1985 1985 }
1986 1986 for (int i = rmIndex0; i <= rmIndex1; i++) {
1987 1987 ec.removed.addElement(elem.getElement(i));
1988 1988 }
1989 1989 if (child0 != null) {
1990 1990 if(removeElements(child0, rmOffs0, rmOffs1)) {
1991 1991 ec.removed.insertElementAt(child0, 0);
1992 1992 ec.index = index0;
1993 1993 }
1994 1994 }
1995 1995 if (child1 != null) {
1996 1996 if(removeElements(child1, rmOffs0, rmOffs1)) {
1997 1997 ec.removed.addElement(child1);
1998 1998 }
1999 1999 }
2000 2000 }
2001 2001 }
2002 2002
2003 2003 // publish changes
2004 2004 pop();
2005 2005
2006 2006 // Return true if we no longer have any children.
2007 2007 if(elem.getElementCount() == (ec.removed.size() -
2008 2008 ec.added.size())) {
2009 2009 return true;
2010 2010 }
2011 2011 }
2012 2012 return false;
2013 2013 }
2014 2014
2015 2015 /**
2016 2016 * Can the two given elements be coelesced together
2017 2017 * into one element?
2018 2018 */
2019 2019 boolean canJoin(Element e0, Element e1) {
2020 2020 if ((e0 == null) || (e1 == null)) {
2021 2021 return false;
2022 2022 }
2023 2023 // Don't join a leaf to a branch.
2024 2024 boolean leaf0 = e0.isLeaf();
2025 2025 boolean leaf1 = e1.isLeaf();
2026 2026 if(leaf0 != leaf1) {
2027 2027 return false;
2028 2028 }
2029 2029 if (leaf0) {
2030 2030 // Only join leaves if the attributes match, otherwise
2031 2031 // style information will be lost.
2032 2032 return e0.getAttributes().isEqual(e1.getAttributes());
2033 2033 }
2034 2034 // Only join non-leafs if the names are equal. This may result
2035 2035 // in loss of style information, but this is typically acceptable
2036 2036 // for non-leafs.
2037 2037 String name0 = e0.getName();
2038 2038 String name1 = e1.getName();
2039 2039 if (name0 != null) {
2040 2040 return name0.equals(name1);
2041 2041 }
2042 2042 if (name1 != null) {
2043 2043 return name1.equals(name0);
2044 2044 }
2045 2045 // Both names null, treat as equal.
2046 2046 return true;
2047 2047 }
2048 2048
2049 2049 /**
2050 2050 * Joins the two elements carving out a hole for the
2051 2051 * given removed range.
2052 2052 */
2053 2053 Element join(Element p, Element left, Element right, int rmOffs0, int rmOffs1) {
2054 2054 if (left.isLeaf() && right.isLeaf()) {
2055 2055 return createLeafElement(p, left.getAttributes(), left.getStartOffset(),
2056 2056 right.getEndOffset());
2057 2057 } else if ((!left.isLeaf()) && (!right.isLeaf())) {
2058 2058 // join two branch elements. This copies the children before
2059 2059 // the removal range on the left element, and after the removal
2060 2060 // range on the right element. The two elements on the edge
2061 2061 // are joined if possible and needed.
2062 2062 Element to = createBranchElement(p, left.getAttributes());
2063 2063 int ljIndex = left.getElementIndex(rmOffs0);
2064 2064 int rjIndex = right.getElementIndex(rmOffs1);
2065 2065 Element lj = left.getElement(ljIndex);
2066 2066 if (lj.getStartOffset() >= rmOffs0) {
2067 2067 lj = null;
2068 2068 }
2069 2069 Element rj = right.getElement(rjIndex);
2070 2070 if (rj.getStartOffset() == rmOffs1) {
2071 2071 rj = null;
2072 2072 }
2073 2073 Vector<Element> children = new Vector<Element>();
2074 2074
2075 2075 // transfer the left
2076 2076 for (int i = 0; i < ljIndex; i++) {
2077 2077 children.addElement(clone(to, left.getElement(i)));
2078 2078 }
2079 2079
2080 2080 // transfer the join/middle
2081 2081 if (canJoin(lj, rj)) {
2082 2082 Element e = join(to, lj, rj, rmOffs0, rmOffs1);
2083 2083 children.addElement(e);
2084 2084 } else {
2085 2085 if (lj != null) {
2086 2086 children.addElement(cloneAsNecessary(to, lj, rmOffs0, rmOffs1));
2087 2087 }
2088 2088 if (rj != null) {
2089 2089 children.addElement(cloneAsNecessary(to, rj, rmOffs0, rmOffs1));
2090 2090 }
2091 2091 }
2092 2092
2093 2093 // transfer the right
2094 2094 int n = right.getElementCount();
2095 2095 for (int i = (rj == null) ? rjIndex : rjIndex + 1; i < n; i++) {
2096 2096 children.addElement(clone(to, right.getElement(i)));
2097 2097 }
2098 2098
2099 2099 // install the children
2100 2100 Element[] c = new Element[children.size()];
2101 2101 children.copyInto(c);
2102 2102 ((BranchElement)to).replace(0, 0, c);
2103 2103 return to;
2104 2104 } else {
2105 2105 throw new StateInvariantError(
2106 2106 "No support to join leaf element with non-leaf element");
2107 2107 }
2108 2108 }
2109 2109
2110 2110 /**
2111 2111 * Creates a copy of this element, with a different
2112 2112 * parent.
2113 2113 *
2114 2114 * @param parent the parent element
2115 2115 * @param clonee the element to be cloned
2116 2116 * @return the copy
2117 2117 */
2118 2118 public Element clone(Element parent, Element clonee) {
2119 2119 if (clonee.isLeaf()) {
2120 2120 return createLeafElement(parent, clonee.getAttributes(),
2121 2121 clonee.getStartOffset(),
2122 2122 clonee.getEndOffset());
2123 2123 }
2124 2124 Element e = createBranchElement(parent, clonee.getAttributes());
2125 2125 int n = clonee.getElementCount();
2126 2126 Element[] children = new Element[n];
2127 2127 for (int i = 0; i < n; i++) {
2128 2128 children[i] = clone(e, clonee.getElement(i));
2129 2129 }
2130 2130 ((BranchElement)e).replace(0, 0, children);
2131 2131 return e;
2132 2132 }
2133 2133
2134 2134 /**
2135 2135 * Creates a copy of this element, with a different
2136 2136 * parent. Children of this element included in the
2137 2137 * removal range will be discarded.
2138 2138 */
2139 2139 Element cloneAsNecessary(Element parent, Element clonee, int rmOffs0, int rmOffs1) {
2140 2140 if (clonee.isLeaf()) {
2141 2141 return createLeafElement(parent, clonee.getAttributes(),
2142 2142 clonee.getStartOffset(),
2143 2143 clonee.getEndOffset());
2144 2144 }
2145 2145 Element e = createBranchElement(parent, clonee.getAttributes());
2146 2146 int n = clonee.getElementCount();
2147 2147 ArrayList<Element> childrenList = new ArrayList<Element>(n);
2148 2148 for (int i = 0; i < n; i++) {
2149 2149 Element elem = clonee.getElement(i);
2150 2150 if (elem.getStartOffset() < rmOffs0 || elem.getEndOffset() > rmOffs1) {
2151 2151 childrenList.add(cloneAsNecessary(e, elem, rmOffs0, rmOffs1));
2152 2152 }
2153 2153 }
2154 2154 Element[] children = new Element[childrenList.size()];
2155 2155 children = childrenList.toArray(children);
2156 2156 ((BranchElement)e).replace(0, 0, children);
2157 2157 return e;
2158 2158 }
2159 2159
2160 2160 /**
2161 2161 * Determines if a fracture needs to be performed. A fracture
2162 2162 * can be thought of as moving the right part of a tree to a
2163 2163 * new location, where the right part is determined by what has
2164 2164 * been inserted. <code>depth</code> is used to indicate a
2165 2165 * JoinToFracture is needed to an element at a depth
2166 2166 * of <code>depth</code>. Where the root is 0, 1 is the children
2167 2167 * of the root...
2168 2168 * <p>This will invoke <code>fractureFrom</code> if it is determined
2169 2169 * a fracture needs to happen.
2170 2170 */
2171 2171 void fracture(int depth) {
2172 2172 int cLength = insertPath.length;
2173 2173 int lastIndex = -1;
2174 2174 boolean needRecreate = recreateLeafs;
2175 2175 ElemChanges lastChange = insertPath[cLength - 1];
2176 2176 // Use childAltered to determine when a child has been altered,
2177 2177 // that is the point of insertion is less than the element count.
2178 2178 boolean childAltered = ((lastChange.index + 1) <
2179 2179 lastChange.parent.getElementCount());
2180 2180 int deepestAlteredIndex = (needRecreate) ? cLength : -1;
2181 2181 int lastAlteredIndex = cLength - 1;
2182 2182
2183 2183 createdFracture = true;
2184 2184 // Determine where to start recreating from.
2185 2185 // Start at - 2, as first one is indicated by recreateLeafs and
2186 2186 // childAltered.
2187 2187 for(int counter = cLength - 2; counter >= 0; counter--) {
2188 2188 ElemChanges change = insertPath[counter];
2189 2189 if(change.added.size() > 0 || counter == depth) {
2190 2190 lastIndex = counter;
2191 2191 if(!needRecreate && childAltered) {
2192 2192 needRecreate = true;
2193 2193 if(deepestAlteredIndex == -1)
2194 2194 deepestAlteredIndex = lastAlteredIndex + 1;
2195 2195 }
2196 2196 }
2197 2197 if(!childAltered && change.index <
2198 2198 change.parent.getElementCount()) {
2199 2199 childAltered = true;
2200 2200 lastAlteredIndex = counter;
2201 2201 }
2202 2202 }
2203 2203 if(needRecreate) {
2204 2204 // Recreate all children to right of parent starting
2205 2205 // at lastIndex.
2206 2206 if(lastIndex == -1)
2207 2207 lastIndex = cLength - 1;
2208 2208 fractureFrom(insertPath, lastIndex, deepestAlteredIndex);
2209 2209 }
2210 2210 }
2211 2211
2212 2212 /**
2213 2213 * Recreates the elements to the right of the insertion point.
2214 2214 * This starts at <code>startIndex</code> in <code>changed</code>,
2215 2215 * and calls duplicate to duplicate existing elements.
2216 2216 * This will also duplicate the elements along the insertion
2217 2217 * point, until a depth of <code>endFractureIndex</code> is
2218 2218 * reached, at which point only the elements to the right of
2219 2219 * the insertion point are duplicated.
2220 2220 */
2221 2221 void fractureFrom(ElemChanges[] changed, int startIndex,
2222 2222 int endFractureIndex) {
2223 2223 // Recreate the element representing the inserted index.
2224 2224 ElemChanges change = changed[startIndex];
2225 2225 Element child;
2226 2226 Element newChild;
2227 2227 int changeLength = changed.length;
2228 2228
2229 2229 if((startIndex + 1) == changeLength)
2230 2230 child = change.parent.getElement(change.index);
2231 2231 else
2232 2232 child = change.parent.getElement(change.index - 1);
2233 2233 if(child.isLeaf()) {
2234 2234 newChild = createLeafElement(change.parent,
2235 2235 child.getAttributes(), Math.max(endOffset,
2236 2236 child.getStartOffset()), child.getEndOffset());
2237 2237 }
2238 2238 else {
2239 2239 newChild = createBranchElement(change.parent,
2240 2240 child.getAttributes());
2241 2241 }
2242 2242 fracturedParent = change.parent;
2243 2243 fracturedChild = newChild;
2244 2244
2245 2245 // Recreate all the elements to the right of the
2246 2246 // insertion point.
2247 2247 Element parent = newChild;
2248 2248
2249 2249 while(++startIndex < endFractureIndex) {
2250 2250 boolean isEnd = ((startIndex + 1) == endFractureIndex);
2251 2251 boolean isEndLeaf = ((startIndex + 1) == changeLength);
2252 2252
2253 2253 // Create the newChild, a duplicate of the elment at
2254 2254 // index. This isn't done if isEnd and offsetLastIndex are true
2255 2255 // indicating a join previous was done.
2256 2256 change = changed[startIndex];
2257 2257
2258 2258 // Determine the child to duplicate, won't have to duplicate
2259 2259 // if at end of fracture, or offseting index.
2260 2260 if(isEnd) {
2261 2261 if(offsetLastIndex || !isEndLeaf)
2262 2262 child = null;
2263 2263 else
2264 2264 child = change.parent.getElement(change.index);
2265 2265 }
2266 2266 else {
2267 2267 child = change.parent.getElement(change.index - 1);
2268 2268 }
2269 2269 // Duplicate it.
2270 2270 if(child != null) {
2271 2271 if(child.isLeaf()) {
2272 2272 newChild = createLeafElement(parent,
2273 2273 child.getAttributes(), Math.max(endOffset,
2274 2274 child.getStartOffset()), child.getEndOffset());
2275 2275 }
2276 2276 else {
2277 2277 newChild = createBranchElement(parent,
2278 2278 child.getAttributes());
2279 2279 }
2280 2280 }
2281 2281 else
2282 2282 newChild = null;
2283 2283
2284 2284 // Recreate the remaining children (there may be none).
2285 2285 int kidsToMove = change.parent.getElementCount() -
2286 2286 change.index;
2287 2287 Element[] kids;
2288 2288 int moveStartIndex;
2289 2289 int kidStartIndex = 1;
2290 2290
2291 2291 if(newChild == null) {
2292 2292 // Last part of fracture.
2293 2293 if(isEndLeaf) {
2294 2294 kidsToMove--;
2295 2295 moveStartIndex = change.index + 1;
2296 2296 }
2297 2297 else {
2298 2298 moveStartIndex = change.index;
2299 2299 }
2300 2300 kidStartIndex = 0;
2301 2301 kids = new Element[kidsToMove];
2302 2302 }
2303 2303 else {
2304 2304 if(!isEnd) {
2305 2305 // Branch.
2306 2306 kidsToMove++;
2307 2307 moveStartIndex = change.index;
2308 2308 }
2309 2309 else {
2310 2310 // Last leaf, need to recreate part of it.
2311 2311 moveStartIndex = change.index + 1;
2312 2312 }
2313 2313 kids = new Element[kidsToMove];
2314 2314 kids[0] = newChild;
2315 2315 }
2316 2316
2317 2317 for(int counter = kidStartIndex; counter < kidsToMove;
2318 2318 counter++) {
2319 2319 Element toMove =change.parent.getElement(moveStartIndex++);
2320 2320 kids[counter] = recreateFracturedElement(parent, toMove);
2321 2321 change.removed.addElement(toMove);
2322 2322 }
2323 2323 ((BranchElement)parent).replace(0, 0, kids);
2324 2324 parent = newChild;
2325 2325 }
2326 2326 }
2327 2327
2328 2328 /**
2329 2329 * Recreates <code>toDuplicate</code>. This is called when an
2330 2330 * element needs to be created as the result of an insertion. This
2331 2331 * will recurse and create all the children. This is similar to
2332 2332 * <code>clone</code>, but deteremines the offsets differently.
2333 2333 */
2334 2334 Element recreateFracturedElement(Element parent, Element toDuplicate) {
2335 2335 if(toDuplicate.isLeaf()) {
2336 2336 return createLeafElement(parent, toDuplicate.getAttributes(),
2337 2337 Math.max(toDuplicate.getStartOffset(),
2338 2338 endOffset),
2339 2339 toDuplicate.getEndOffset());
2340 2340 }
2341 2341 // Not a leaf
2342 2342 Element newParent = createBranchElement(parent, toDuplicate.
2343 2343 getAttributes());
2344 2344 int childCount = toDuplicate.getElementCount();
2345 2345 Element[] newKids = new Element[childCount];
2346 2346 for(int counter = 0; counter < childCount; counter++) {
2347 2347 newKids[counter] = recreateFracturedElement(newParent,
2348 2348 toDuplicate.getElement(counter));
2349 2349 }
2350 2350 ((BranchElement)newParent).replace(0, 0, newKids);
2351 2351 return newParent;
2352 2352 }
2353 2353
2354 2354 /**
2355 2355 * Splits the bottommost leaf in <code>path</code>.
2356 2356 * This is called from insert when the first element is NOT content.
2357 2357 */
2358 2358 void fractureDeepestLeaf(ElementSpec[] specs) {
2359 2359 // Split the bottommost leaf. It will be recreated elsewhere.
2360 2360 ElemChanges ec = path.peek();
2361 2361 Element child = ec.parent.getElement(ec.index);
2362 2362 // Inserts at offset 0 do not need to recreate child (it would
2363 2363 // have a length of 0!).
2364 2364 if (offset != 0) {
2365 2365 Element newChild = createLeafElement(ec.parent,
2366 2366 child.getAttributes(),
2367 2367 child.getStartOffset(),
2368 2368 offset);
2369 2369
2370 2370 ec.added.addElement(newChild);
2371 2371 }
2372 2372 ec.removed.addElement(child);
2373 2373 if(child.getEndOffset() != endOffset)
2374 2374 recreateLeafs = true;
2375 2375 else
2376 2376 offsetLastIndex = true;
2377 2377 }
2378 2378
2379 2379 /**
2380 2380 * Inserts the first content. This needs to be separate to handle
2381 2381 * joining.
2382 2382 */
2383 2383 void insertFirstContent(ElementSpec[] specs) {
2384 2384 ElementSpec firstSpec = specs[0];
2385 2385 ElemChanges ec = path.peek();
2386 2386 Element child = ec.parent.getElement(ec.index);
2387 2387 int firstEndOffset = offset + firstSpec.getLength();
2388 2388 boolean isOnlyContent = (specs.length == 1);
2389 2389
2390 2390 switch(firstSpec.getDirection()) {
2391 2391 case ElementSpec.JoinPreviousDirection:
2392 2392 if(child.getEndOffset() != firstEndOffset &&
2393 2393 !isOnlyContent) {
2394 2394 // Create the left split part containing new content.
2395 2395 Element newE = createLeafElement(ec.parent,
2396 2396 child.getAttributes(), child.getStartOffset(),
2397 2397 firstEndOffset);
2398 2398 ec.added.addElement(newE);
2399 2399 ec.removed.addElement(child);
2400 2400 // Remainder will be created later.
2401 2401 if(child.getEndOffset() != endOffset)
2402 2402 recreateLeafs = true;
2403 2403 else
2404 2404 offsetLastIndex = true;
2405 2405 }
2406 2406 else {
2407 2407 offsetLastIndex = true;
2408 2408 offsetLastIndexOnReplace = true;
2409 2409 }
2410 2410 // else Inserted at end, and is total length.
2411 2411 // Update index incase something added/removed.
2412 2412 break;
2413 2413 case ElementSpec.JoinNextDirection:
2414 2414 if(offset != 0) {
2415 2415 // Recreate the first element, its offset will have
2416 2416 // changed.
2417 2417 Element newE = createLeafElement(ec.parent,
2418 2418 child.getAttributes(), child.getStartOffset(),
2419 2419 offset);
2420 2420 ec.added.addElement(newE);
2421 2421 // Recreate the second, merge part. We do no checking
2422 2422 // to see if JoinNextDirection is valid here!
2423 2423 Element nextChild = ec.parent.getElement(ec.index + 1);
2424 2424 if(isOnlyContent)
2425 2425 newE = createLeafElement(ec.parent, nextChild.
2426 2426 getAttributes(), offset, nextChild.getEndOffset());
2427 2427 else
2428 2428 newE = createLeafElement(ec.parent, nextChild.
2429 2429 getAttributes(), offset, firstEndOffset);
2430 2430 ec.added.addElement(newE);
2431 2431 ec.removed.addElement(child);
2432 2432 ec.removed.addElement(nextChild);
2433 2433 }
2434 2434 // else nothin to do.
2435 2435 // PENDING: if !isOnlyContent could raise here!
2436 2436 break;
2437 2437 default:
2438 2438 // Inserted into middle, need to recreate split left
2439 2439 // new content, and split right.
2440 2440 if(child.getStartOffset() != offset) {
2441 2441 Element newE = createLeafElement(ec.parent,
2442 2442 child.getAttributes(), child.getStartOffset(),
2443 2443 offset);
2444 2444 ec.added.addElement(newE);
2445 2445 }
2446 2446 ec.removed.addElement(child);
2447 2447 // new content
2448 2448 Element newE = createLeafElement(ec.parent,
2449 2449 firstSpec.getAttributes(),
2450 2450 offset, firstEndOffset);
2451 2451 ec.added.addElement(newE);
2452 2452 if(child.getEndOffset() != endOffset) {
2453 2453 // Signals need to recreate right split later.
2454 2454 recreateLeafs = true;
2455 2455 }
2456 2456 else {
2457 2457 offsetLastIndex = true;
2458 2458 }
2459 2459 break;
2460 2460 }
2461 2461 }
2462 2462
2463 2463 Element root;
2464 2464 transient int pos; // current position
2465 2465 transient int offset;
2466 2466 transient int length;
2467 2467 transient int endOffset;
2468 2468 transient Vector<ElemChanges> changes;
2469 2469 transient Stack<ElemChanges> path;
2470 2470 transient boolean insertOp;
2471 2471
2472 2472 transient boolean recreateLeafs; // For insert.
2473 2473
2474 2474 /** For insert, path to inserted elements. */
2475 2475 transient ElemChanges[] insertPath;
2476 2476 /** Only for insert, set to true when the fracture has been created. */
2477 2477 transient boolean createdFracture;
2478 2478 /** Parent that contains the fractured child. */
2479 2479 transient Element fracturedParent;
2480 2480 /** Fractured child. */
2481 2481 transient Element fracturedChild;
2482 2482 /** Used to indicate when fracturing that the last leaf should be
2483 2483 * skipped. */
2484 2484 transient boolean offsetLastIndex;
2485 2485 /** Used to indicate that the parent of the deepest leaf should
2486 2486 * offset the index by 1 when adding/removing elements in an
2487 2487 * insert. */
2488 2488 transient boolean offsetLastIndexOnReplace;
2489 2489
2490 2490 /*
2491 2491 * Internal record used to hold element change specifications
2492 2492 */
2493 2493 class ElemChanges {
2494 2494
2495 2495 ElemChanges(Element parent, int index, boolean isFracture) {
2496 2496 this.parent = parent;
2497 2497 this.index = index;
2498 2498 this.isFracture = isFracture;
2499 2499 added = new Vector<Element>();
2500 2500 removed = new Vector<Element>();
2501 2501 }
2502 2502
2503 2503 public String toString() {
2504 2504 return "added: " + added + "\nremoved: " + removed + "\n";
2505 2505 }
2506 2506
2507 2507 Element parent;
2508 2508 int index;
2509 2509 Vector<Element> added;
2510 2510 Vector<Element> removed;
2511 2511 boolean isFracture;
2512 2512 }
2513 2513
2514 2514 }
2515 2515
2516 2516 /**
2517 2517 * An UndoableEdit used to remember AttributeSet changes to an
2518 2518 * Element.
2519 2519 */
2520 2520 public static class AttributeUndoableEdit extends AbstractUndoableEdit {
2521 2521 public AttributeUndoableEdit(Element element, AttributeSet newAttributes,
2522 2522 boolean isReplacing) {
2523 2523 super();
2524 2524 this.element = element;
2525 2525 this.newAttributes = newAttributes;
2526 2526 this.isReplacing = isReplacing;
2527 2527 // If not replacing, it may be more efficient to only copy the
2528 2528 // changed values...
2529 2529 copy = element.getAttributes().copyAttributes();
2530 2530 }
2531 2531
2532 2532 /**
2533 2533 * Redoes a change.
2534 2534 *
2535 2535 * @exception CannotRedoException if the change cannot be redone
2536 2536 */
2537 2537 public void redo() throws CannotRedoException {
2538 2538 super.redo();
2539 2539 MutableAttributeSet as = (MutableAttributeSet)element
2540 2540 .getAttributes();
2541 2541 if(isReplacing)
2542 2542 as.removeAttributes(as);
2543 2543 as.addAttributes(newAttributes);
2544 2544 }
2545 2545
2546 2546 /**
2547 2547 * Undoes a change.
2548 2548 *
2549 2549 * @exception CannotUndoException if the change cannot be undone
2550 2550 */
2551 2551 public void undo() throws CannotUndoException {
2552 2552 super.undo();
2553 2553 MutableAttributeSet as = (MutableAttributeSet)element.getAttributes();
2554 2554 as.removeAttributes(as);
2555 2555 as.addAttributes(copy);
2556 2556 }
2557 2557
2558 2558 // AttributeSet containing additional entries, must be non-mutable!
2559 2559 protected AttributeSet newAttributes;
2560 2560 // Copy of the AttributeSet the Element contained.
2561 2561 protected AttributeSet copy;
2562 2562 // true if all the attributes in the element were removed first.
2563 2563 protected boolean isReplacing;
2564 2564 // Efected Element.
2565 2565 protected Element element;
2566 2566 }
2567 2567
2568 2568 /**
2569 2569 * UndoableEdit for changing the resolve parent of an Element.
2570 2570 */
2571 2571 static class StyleChangeUndoableEdit extends AbstractUndoableEdit {
2572 2572 public StyleChangeUndoableEdit(AbstractElement element,
2573 2573 Style newStyle) {
2574 2574 super();
2575 2575 this.element = element;
2576 2576 this.newStyle = newStyle;
2577 2577 oldStyle = element.getResolveParent();
2578 2578 }
2579 2579
2580 2580 /**
2581 2581 * Redoes a change.
2582 2582 *
2583 2583 * @exception CannotRedoException if the change cannot be redone
2584 2584 */
2585 2585 public void redo() throws CannotRedoException {
2586 2586 super.redo();
2587 2587 element.setResolveParent(newStyle);
2588 2588 }
2589 2589
2590 2590 /**
2591 2591 * Undoes a change.
2592 2592 *
2593 2593 * @exception CannotUndoException if the change cannot be undone
2594 2594 */
2595 2595 public void undo() throws CannotUndoException {
2596 2596 super.undo();
2597 2597 element.setResolveParent(oldStyle);
2598 2598 }
2599 2599
2600 2600 /** Element to change resolve parent of. */
2601 2601 protected AbstractElement element;
2602 2602 /** New style. */
2603 2603 protected Style newStyle;
2604 2604 /** Old style, before setting newStyle. */
2605 2605 protected AttributeSet oldStyle;
2606 2606 }
2607 2607
2608 2608 /**
2609 2609 * Base class for style change handlers with support for stale objects detection.
2610 2610 */
2611 2611 abstract static class AbstractChangeHandler implements ChangeListener {
2612 2612
2613 2613 /* This has an implicit reference to the handler object. */
2614 2614 private class DocReference extends WeakReference<DefaultStyledDocument> {
2615 2615
2616 2616 DocReference(DefaultStyledDocument d, ReferenceQueue<DefaultStyledDocument> q) {
2617 2617 super(d, q);
2618 2618 }
2619 2619
2620 2620 /**
2621 2621 * Return a reference to the style change handler object.
2622 2622 */
2623 2623 ChangeListener getListener() {
2624 2624 return AbstractChangeHandler.this;
2625 2625 }
2626 2626 }
2627 2627
2628 2628 /** Class-specific reference queues. */
2629 2629 private final static Map<Class, ReferenceQueue<DefaultStyledDocument>> queueMap
2630 2630 = new HashMap<Class, ReferenceQueue<DefaultStyledDocument>>();
2631 2631
2632 2632 /** A weak reference to the document object. */
2633 2633 private DocReference doc;
2634 2634
2635 2635 AbstractChangeHandler(DefaultStyledDocument d) {
2636 2636 Class c = getClass();
2637 2637 ReferenceQueue<DefaultStyledDocument> q;
2638 2638 synchronized (queueMap) {
2639 2639 q = queueMap.get(c);
2640 2640 if (q == null) {
2641 2641 q = new ReferenceQueue<DefaultStyledDocument>();
2642 2642 queueMap.put(c, q);
2643 2643 }
2644 2644 }
2645 2645 doc = new DocReference(d, q);
2646 2646 }
2647 2647
2648 2648 /**
2649 2649 * Return a list of stale change listeners.
2650 2650 *
2651 2651 * A change listener becomes "stale" when its document is cleaned by GC.
2652 2652 */
2653 2653 static List<ChangeListener> getStaleListeners(ChangeListener l) {
2654 2654 List<ChangeListener> staleListeners = new ArrayList<ChangeListener>();
2655 2655 ReferenceQueue<DefaultStyledDocument> q = queueMap.get(l.getClass());
2656 2656
2657 2657 if (q != null) {
2658 2658 DocReference r;
2659 2659 synchronized (q) {
2660 2660 while ((r = (DocReference) q.poll()) != null) {
2661 2661 staleListeners.add(r.getListener());
2662 2662 }
2663 2663 }
2664 2664 }
2665 2665
2666 2666 return staleListeners;
2667 2667 }
2668 2668
2669 2669 /**
2670 2670 * The ChangeListener wrapper which guards against dead documents.
2671 2671 */
2672 2672 public void stateChanged(ChangeEvent e) {
2673 2673 DefaultStyledDocument d = doc.get();
2674 2674 if (d != null) {
2675 2675 fireStateChanged(d, e);
2676 2676 }
2677 2677 }
2678 2678
2679 2679 /** Run the actual class-specific stateChanged() method. */
2680 2680 abstract void fireStateChanged(DefaultStyledDocument d, ChangeEvent e);
2681 2681 }
2682 2682
2683 2683 /**
2684 2684 * Added to all the Styles. When instances of this receive a
2685 2685 * stateChanged method, styleChanged is invoked.
2686 2686 */
2687 2687 static class StyleChangeHandler extends AbstractChangeHandler {
2688 2688
2689 2689 StyleChangeHandler(DefaultStyledDocument d) {
2690 2690 super(d);
2691 2691 }
2692 2692
2693 2693 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2694 2694 Object source = e.getSource();
2695 2695 if (source instanceof Style) {
2696 2696 d.styleChanged((Style) source);
2697 2697 } else {
2698 2698 d.styleChanged(null);
2699 2699 }
2700 2700 }
2701 2701 }
2702 2702
2703 2703
2704 2704 /**
2705 2705 * Added to the StyleContext. When the StyleContext changes, this invokes
2706 2706 * <code>updateStylesListeningTo</code>.
2707 2707 */
2708 2708 static class StyleContextChangeHandler extends AbstractChangeHandler {
2709 2709
2710 2710 StyleContextChangeHandler(DefaultStyledDocument d) {
2711 2711 super(d);
2712 2712 }
2713 2713
2714 2714 void fireStateChanged(DefaultStyledDocument d, ChangeEvent e) {
2715 2715 d.updateStylesListeningTo();
2716 2716 }
2717 2717 }
2718 2718
2719 2719
2720 2720 /**
2721 2721 * When run this creates a change event for the complete document
2722 2722 * and fires it.
2723 2723 */
2724 2724 class ChangeUpdateRunnable implements Runnable {
2725 2725 boolean isPending = false;
2726 2726
2727 2727 public void run() {
2728 2728 synchronized(this) {
2729 2729 isPending = false;
2730 2730 }
2731 2731
2732 2732 try {
2733 2733 writeLock();
2734 2734 DefaultDocumentEvent dde = new DefaultDocumentEvent(0,
2735 2735 getLength(),
2736 2736 DocumentEvent.EventType.CHANGE);
2737 2737 dde.end();
2738 2738 fireChangedUpdate(dde);
2739 2739 } finally {
2740 2740 writeUnlock();
2741 2741 }
2742 2742 }
2743 2743 }
2744 2744 }
↓ open down ↓ |
2195 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX