Source for javax.swing.text.GapContent

   1: /* GapContent.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.text;
  40: 
  41: import java.io.Serializable;
  42: import java.lang.ref.WeakReference;
  43: import java.util.ArrayList;
  44: import java.util.Collections;
  45: import java.util.Comparator;
  46: import java.util.Iterator;
  47: import java.util.ListIterator;
  48: import java.util.Vector;
  49: 
  50: import javax.swing.undo.AbstractUndoableEdit;
  51: import javax.swing.undo.CannotRedoException;
  52: import javax.swing.undo.CannotUndoException;
  53: import javax.swing.undo.UndoableEdit;
  54: 
  55: /**
  56:  * This implementation of {@link AbstractDocument.Content} uses a gapped buffer.
  57:  * This takes advantage of the fact that text area content is mostly inserted
  58:  * sequentially. The buffer is a char array that maintains a gap at the current
  59:  * insertion point. If characters a inserted at gap boundaries, the cost is
  60:  * minimal (simple array access). The array only has to be shifted around when
  61:  * the insertion point moves (then the gap also moves and one array copy is
  62:  * necessary) or when the gap is filled up and the buffer has to be enlarged.
  63:  * 
  64:  * TODO: Implement UndoableEdit support stuff
  65:  */
  66: public class GapContent
  67:     implements AbstractDocument.Content, Serializable
  68: {
  69:   
  70:   /**
  71:    * A {@link Position} implementation for <code>GapContent</code>.
  72:    */
  73:   private class GapContentPosition
  74:     implements Position, Comparable
  75:   {
  76: 
  77:     /** The index within the buffer array. */
  78:     int mark;
  79: 
  80:     /**
  81:      * Creates a new GapContentPosition object.
  82:      * 
  83:      * @param mark the mark of this Position
  84:      */
  85:     GapContentPosition(int mark)
  86:     {
  87:       this.mark = mark;
  88:     }
  89: 
  90:     /**
  91:      * Comparable interface implementation. This is used to store all
  92:      * positions in an ordered fashion.
  93:      * 
  94:      * @param o the object to be compared to this
  95:      * 
  96:      * @return a negative integer if this is less than <code>o</code>, zero
  97:      *         if both are equal or a positive integer if this is greater than
  98:      *         <code>o</code>
  99:      * 
 100:      * @throws ClassCastException if <code>o</code> is not a
 101:      *         GapContentPosition or Integer object
 102:      */
 103:     public int compareTo(Object o)
 104:     {
 105:       if (o instanceof Integer)
 106:         {
 107:           int otherMark = ((Integer) o).intValue();
 108:           return mark - otherMark;
 109:         }
 110:       else
 111:         {
 112:           GapContentPosition other = (GapContentPosition) o;
 113:           return mark - other.mark;
 114:         }
 115:     }
 116: 
 117:     /**
 118:      * Returns the current offset of this Position within the content.
 119:      * 
 120:      * @return the current offset of this Position within the content.
 121:      */
 122:     public int getOffset()
 123:     {
 124:       // Check precondition.
 125:       assert mark <= gapStart || mark >= gapEnd : "mark: " + mark
 126:                                                + ", gapStart: " + gapStart
 127:                                                + ", gapEnd: " + gapEnd;
 128:       if (mark <= gapStart)
 129:         return mark;
 130:       else
 131:         return mark - (gapEnd - gapStart);
 132:     }
 133:   }
 134: 
 135:   private class InsertUndo extends AbstractUndoableEdit
 136:   {
 137:     public int where, length;
 138:     String text;
 139:     public InsertUndo(int start, int len)
 140:     {
 141:       where = start;
 142:       length = len;
 143:     }
 144: 
 145:     public void undo () throws CannotUndoException
 146:     {
 147:       super.undo();
 148:       try
 149:       {
 150:         text = getString(where, length);
 151:         remove(where, length);
 152:       }
 153:       catch (BadLocationException ble)
 154:       {
 155:         throw new CannotUndoException();
 156:       }
 157:     }
 158:     
 159:     public void redo () throws CannotUndoException
 160:     {
 161:       super.redo();
 162:       try
 163:       {
 164:         insertString(where, text);
 165:       }
 166:       catch (BadLocationException ble)
 167:       {
 168:         throw new CannotRedoException();
 169:       }
 170:     }
 171:     
 172:   }
 173:   
 174:   private class UndoRemove extends AbstractUndoableEdit
 175:   {
 176:     public int where;
 177:     String text;
 178:     public UndoRemove(int start, String removedText)
 179:     {
 180:       where = start;
 181:       text = removedText;
 182:     }
 183: 
 184:     public void undo () throws CannotUndoException
 185:     {
 186:       super.undo();
 187:       try
 188:       {
 189:         insertString(where, text);
 190:       }
 191:       catch (BadLocationException ble)
 192:       {
 193:         throw new CannotUndoException();
 194:       }
 195:     }
 196:     
 197:     public void redo () throws CannotUndoException
 198:     {
 199:       super.redo();
 200:       try
 201:       {
 202:         remove(where, text.length());
 203:       }
 204:       catch (BadLocationException ble)
 205:       {
 206:         throw new CannotRedoException();
 207:       }
 208:     }
 209:     
 210:   }
 211: 
 212:   /**
 213:    * Compares WeakReference objects in a List by comparing the referenced
 214:    * objects instead.
 215:    *
 216:    * @author Roman Kennke (kennke@aicas.com)
 217:    */
 218:   private class WeakPositionComparator
 219:     implements Comparator
 220:   {
 221: 
 222:     /**
 223:      * Compares two objects of type WeakReference. The objects are compared
 224:      * using the referenced objects compareTo() method.
 225:      */
 226:     public int compare(Object o1, Object o2)
 227:     {
 228:       // Unwrap references.
 229:       if (o1 instanceof WeakReference)
 230:         o1 = ((WeakReference) o1).get();
 231:       if (o2 instanceof WeakReference)
 232:         o2 = ((WeakReference) o2).get();
 233: 
 234:       GapContentPosition p1 = (GapContentPosition) o1;
 235:       GapContentPosition p2 = (GapContentPosition) o2;
 236: 
 237:       int retVal;
 238:       if (p1 == null || p2 == null)
 239:         retVal = -1;
 240:       else
 241:         retVal = p1.compareTo(p2);
 242:       return retVal;
 243:     }
 244:   }
 245: 
 246:   /** The serialization UID (compatible with JDK1.5). */
 247:   private static final long serialVersionUID = -6226052713477823730L;
 248: 
 249:   /**
 250:    * This is the default buffer size and the amount of bytes that a buffer is
 251:    * extended if it is full.
 252:    */
 253:   static final int DEFAULT_BUFSIZE = 10;
 254: 
 255:   /**
 256:    * The text buffer.
 257:    */
 258:   char[] buffer;
 259: 
 260:   /**
 261:    * The index of the first character of the gap.
 262:    */
 263:   int gapStart;
 264: 
 265:   /**
 266:    * The index of the character after the last character of the gap.
 267:    */
 268:   int gapEnd;
 269: 
 270:   /**
 271:    * The positions generated by this GapContent. They are kept in an ordered
 272:    * fashion, so they can be looked up easily. The value objects will be
 273:    * WeakReference objects that in turn hold GapContentPosition objects.
 274:    */
 275:   private ArrayList positions;
 276: 
 277:   /**
 278:    * Creates a new GapContent object.
 279:    */
 280:   public GapContent()
 281:   {
 282:     this(DEFAULT_BUFSIZE);
 283:   }
 284: 
 285:   /**
 286:    * Creates a new GapContent object with a specified initial size.
 287:    * 
 288:    * @param size the initial size of the buffer
 289:    */
 290:   public GapContent(int size)
 291:   {
 292:     size = Math.max(size, 2);
 293:     buffer = (char[]) allocateArray(size);
 294:     gapStart = 1;
 295:     gapEnd = size;
 296:     buffer[0] = '\n';
 297:     positions = new ArrayList();
 298:   }
 299: 
 300:   /**
 301:    * Allocates an array of the specified length that can then be used as
 302:    * buffer.
 303:    * 
 304:    * @param size the size of the array to be allocated
 305:    * 
 306:    * @return the allocated array
 307:    */
 308:   protected Object allocateArray(int size)
 309:   {
 310:     return new char[size];
 311:   }
 312: 
 313:   /**
 314:    * Returns the length of the allocated buffer array.
 315:    * 
 316:    * @return the length of the allocated buffer array
 317:    */
 318:   protected int getArrayLength()
 319:   {
 320:     return buffer.length;
 321:   }
 322: 
 323:   /**
 324:    * Returns the length of the content.
 325:    * 
 326:    * @return the length of the content
 327:    */
 328:   public int length()
 329:   {
 330:     return buffer.length - (gapEnd - gapStart);
 331:   }
 332: 
 333:   /**
 334:    * Inserts a string at the specified position.
 335:    * 
 336:    * @param where the position where the string is inserted
 337:    * @param str the string that is to be inserted
 338:    * 
 339:    * @return an UndoableEdit object
 340:    * 
 341:    * @throws BadLocationException if <code>where</code> is not a valid
 342:    *         location in the buffer
 343:    */
 344:   public UndoableEdit insertString(int where, String str)
 345:       throws BadLocationException
 346:   {
 347:     // check arguments
 348:     int length = length();
 349:     int strLen = str.length();
 350: 
 351:     if (where < 0)
 352:       throw new BadLocationException("The where argument cannot be smaller"
 353:                                      + " than the zero", where);
 354: 
 355:     if (where > length)
 356:       throw new BadLocationException("The where argument cannot be greater"
 357:           + " than the content length", where);
 358: 
 359:     replace(where, 0, str.toCharArray(), strLen);
 360: 
 361:     return new InsertUndo(where, strLen);
 362:   }
 363: 
 364:   /**
 365:    * Removes a piece of content at th specified position.
 366:    * 
 367:    * @param where the position where the content is to be removed
 368:    * @param nitems number of characters to be removed
 369:    * 
 370:    * @return an UndoableEdit object
 371:    * 
 372:    * @throws BadLocationException if <code>where</code> is not a valid
 373:    *         location in the buffer
 374:    */
 375:   public UndoableEdit remove(int where, int nitems) throws BadLocationException
 376:   {
 377:     // check arguments
 378:     int length = length();
 379:     
 380:     if ((where + nitems) >= length)
 381:       throw new BadLocationException("where + nitems cannot be greater"
 382:           + " than the content length", where + nitems);
 383:     
 384:     String removedText = getString(where, nitems);
 385:     replace(where, nitems, null, 0);
 386: 
 387:     return new UndoRemove(where, removedText);
 388:   }
 389: 
 390:   /**
 391:    * Returns a piece of content as String.
 392:    * 
 393:    * @param where the start location of the fragment
 394:    * @param len the length of the fragment
 395:    * 
 396:    * @throws BadLocationException if <code>where</code> or
 397:    *         <code>where + len</code> are no valid locations in the buffer
 398:    */
 399:   public String getString(int where, int len) throws BadLocationException
 400:   {
 401:     Segment seg = new Segment();
 402:     try
 403:       {
 404:         getChars(where, len, seg);
 405:         return new String(seg.array, seg.offset, seg.count);
 406:       }
 407:     catch (StringIndexOutOfBoundsException ex)
 408:       {
 409:         int invalid = 0;
 410:         if (seg.offset < 0 || seg.offset >= seg.array.length)
 411:           invalid = seg.offset;
 412:         else
 413:           invalid = seg.offset + seg.count;
 414:         throw new BadLocationException("Illegal location: array.length = "
 415:                                        + seg.array.length + ", offset = "
 416:                                        + seg.offset + ", count = "
 417:                                        + seg.count, invalid);
 418:       }
 419:   }
 420: 
 421:   /**
 422:    * Fetches a piece of content and stores it in a {@link Segment} object.
 423:    * 
 424:    * If the requested piece of text spans the gap, the content is copied into a
 425:    * new array. If it doesn't then it is contiguous and the actual content
 426:    * store is returned.
 427:    * 
 428:    * @param where the start location of the fragment
 429:    * @param len the length of the fragment
 430:    * @param txt the Segment object to store the fragment in
 431:    * 
 432:    * @throws BadLocationException if <code>where</code> or
 433:    *         <code>where + len</code> are no valid locations in the buffer
 434:    */
 435:   public void getChars(int where, int len, Segment txt)
 436:       throws BadLocationException
 437:   {
 438:     // check arguments
 439:     int length = length();
 440:     if (where < 0)
 441:       throw new BadLocationException("the where argument may not be below zero", where);
 442:     if (where >= length)
 443:       throw new BadLocationException("the where argument cannot be greater"
 444:           + " than the content length", where);
 445:     if ((where + len) > length)
 446:       throw new BadLocationException("len plus where cannot be greater"
 447:           + " than the content length", len + where);
 448: 
 449:     // check if requested segment is contiguous
 450:     if ((where < gapStart) && ((gapStart - where) < len))
 451:     {
 452:       // requested segment is not contiguous -> copy the pieces together
 453:       char[] copy = new char[len];
 454:       int lenFirst = gapStart - where; // the length of the first segment
 455:       System.arraycopy(buffer, where, copy, 0, lenFirst);
 456:       System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst);
 457:       txt.array = copy;
 458:       txt.offset = 0;
 459:       txt.count = len;
 460:     }
 461:     else
 462:     {
 463:       // requested segment is contiguous -> we can simply return the
 464:       // actual content
 465:       txt.array = buffer;
 466:       if (where < gapStart)
 467:         txt.offset = where;
 468:       else
 469:         txt.offset = where + (gapEnd - gapStart);
 470:       txt.count = len;
 471:     }
 472:   }
 473: 
 474:   /**
 475:    * Creates and returns a mark at the specified position.
 476:    * 
 477:    * @param offset the position at which to create the mark
 478:    * 
 479:    * @return the create Position object for the mark
 480:    * 
 481:    * @throws BadLocationException if the offset is not a valid position in the
 482:    *         buffer
 483:    */
 484:   public Position createPosition(final int offset) throws BadLocationException
 485:   {
 486:     if (offset < 0 || offset > length())
 487:       throw new BadLocationException("The offset was out of the bounds of this"
 488:           + " buffer", offset);
 489: 
 490:     clearPositionReferences();
 491: 
 492:     // We store the actual array index in the GapContentPosition. The real
 493:     // offset is then calculated in the GapContentPosition.
 494:     int mark = offset;
 495:     if (offset >= gapStart)
 496:       mark += gapEnd - gapStart;
 497:     GapContentPosition pos = new GapContentPosition(mark);
 498:     WeakReference r = new WeakReference(pos);
 499: 
 500:     // Add this into our list in a sorted fashion.
 501:     int index = Collections.binarySearch(positions, r,
 502:                                          new WeakPositionComparator());
 503:     if (index < 0)
 504:       index = -(index + 1);
 505:     positions.add(index, r);
 506:     return pos;
 507:   }
 508: 
 509:   /**
 510:    * Enlarges the gap. This allocates a new bigger buffer array, copy the
 511:    * segment before the gap as it is and the segment after the gap at the end
 512:    * of the new buffer array. This does change the gapEnd mark but not the
 513:    * gapStart mark.
 514:    * 
 515:    * @param newSize the new size of the gap
 516:    */
 517:   protected void shiftEnd(int newSize)
 518:   {
 519:     assert newSize > (gapEnd - gapStart) : "The new gap size must be greater "
 520:                                            + "than the old gap size";
 521: 
 522:     int delta = newSize - gapEnd + gapStart;
 523:     // Update the marks after the gapEnd.
 524:     adjustPositionsInRange(gapEnd, buffer.length - gapEnd, delta);
 525: 
 526:     // Copy the data around.
 527:     char[] newBuf = (char[]) allocateArray(length() + newSize);
 528:     System.arraycopy(buffer, 0, newBuf, 0, gapStart);
 529:     System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length
 530:         - gapEnd);
 531:     gapEnd = gapStart + newSize;
 532:     buffer = newBuf;
 533: 
 534:   }
 535: 
 536:   /**
 537:    * Shifts the gap to the specified position.
 538:    * 
 539:    * @param newGapStart the new start position of the gap
 540:    */
 541:   protected void shiftGap(int newGapStart)
 542:   {
 543:     if (newGapStart == gapStart)
 544:       return;
 545: 
 546:     int newGapEnd = newGapStart + gapEnd - gapStart;
 547:     if (newGapStart < gapStart)
 548:       {
 549:         // Update the positions between newGapStart and (old) gapStart. The marks
 550:         // must be shifted by (gapEnd - gapStart).
 551:         adjustPositionsInRange(newGapStart, gapStart - newGapStart, gapEnd - gapStart);
 552:         System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart
 553:                          - newGapStart);
 554:         gapStart = newGapStart;
 555:         gapEnd = newGapEnd;
 556:       }
 557:     else
 558:       {
 559:         // Update the positions between newGapEnd and (old) gapEnd. The marks
 560:         // must be shifted by (gapEnd - gapStart).
 561:         adjustPositionsInRange(gapEnd, newGapEnd - gapEnd, -(gapEnd - gapStart));
 562:         System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart
 563:                          - gapStart);
 564:         gapStart = newGapStart;
 565:         gapEnd = newGapEnd;
 566:       }
 567:     if (gapStart == 0)
 568:       resetMarksAtZero();
 569:   }
 570: 
 571:   /**
 572:    * Shifts the gap start downwards. This does not affect the content of the
 573:    * buffer. This only updates the gap start and all the marks that are between
 574:    * the old gap start and the new gap start. They all are squeezed to the start
 575:    * of the gap, because their location has been removed.
 576:    *
 577:    * @param newGapStart the new gap start
 578:    */
 579:   protected void shiftGapStartDown(int newGapStart)
 580:   {
 581:     if (newGapStart == gapStart)
 582:       return;
 583: 
 584:     assert newGapStart < gapStart : "The new gap start must be less than the "
 585:                                     + "old gap start.";
 586:     setPositionsInRange(newGapStart, gapStart - newGapStart, gapStart);
 587:     gapStart = newGapStart;
 588:   }
 589: 
 590:   /**
 591:    * Shifts the gap end upwards. This does not affect the content of the
 592:    * buffer. This only updates the gap end and all the marks that are between
 593:    * the old gap end and the new end start. They all are squeezed to the end
 594:    * of the gap, because their location has been removed.
 595:    *
 596:    * @param newGapEnd the new gap start
 597:    */
 598:   protected void shiftGapEndUp(int newGapEnd)
 599:   {
 600:     if (newGapEnd == gapEnd)
 601:       return;
 602: 
 603:     assert newGapEnd > gapEnd : "The new gap end must be greater than the "
 604:                                 + "old gap end.";
 605:     setPositionsInRange(gapEnd, newGapEnd - gapEnd, newGapEnd);
 606:     gapEnd = newGapEnd;
 607:   }
 608: 
 609:   /**
 610:    * Returns the allocated buffer array.
 611:    * 
 612:    * @return the allocated buffer array
 613:    */
 614:   protected final Object getArray()
 615:   {
 616:     return buffer;
 617:   }
 618: 
 619:   /**
 620:    * Replaces a portion of the storage with the specified items.
 621:    * 
 622:    * @param position the position at which to remove items
 623:    * @param rmSize the number of items to remove
 624:    * @param addItems the items to add at location
 625:    * @param addSize the number of items to add
 626:    */
 627:   protected void replace(int position, int rmSize, Object addItems,
 628:                          int addSize)
 629:   {
 630:     if (gapStart != position)
 631:       shiftGap(position);
 632:       
 633:     // Remove content
 634:     if (rmSize > 0) 
 635:       shiftGapEndUp(gapEnd + rmSize);
 636: 
 637:     // If gap is too small, enlarge the gap.
 638:     if ((gapEnd - gapStart) <= addSize)
 639:       shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE);
 640: 
 641:     // Add new items to the buffer.
 642:     if (addItems != null)
 643:       {
 644:         System.arraycopy(addItems, 0, buffer, gapStart, addSize);
 645:         
 646:         
 647:         resetMarksAtZero();
 648:         
 649:         gapStart += addSize;
 650:       }
 651:   }
 652: 
 653:   /**
 654:    * Returns the start index of the gap within the buffer array.
 655:    *
 656:    * @return the start index of the gap within the buffer array
 657:    */
 658:   protected final int getGapStart()
 659:   {
 660:     return gapStart;
 661:   }
 662: 
 663:   /**
 664:    * Returns the end index of the gap within the buffer array.
 665:    *
 666:    * @return the end index of the gap within the buffer array
 667:    */
 668:   protected final int getGapEnd()
 669:   {
 670:     return gapEnd;
 671:   }
 672: 
 673:   /**
 674:    * Returns all <code>Position</code>s that are in the range specified by
 675:    * <code>offset</code> and </code>length</code> within the buffer array.
 676:    *
 677:    * @param v the vector to use; if <code>null</code>, a new Vector is allocated
 678:    * @param offset the start offset of the range to search
 679:    * @param length the length of the range to search
 680:    *
 681:    * @return the positions within the specified range
 682:    */
 683:   protected Vector getPositionsInRange(Vector v, int offset, int length)
 684:   {
 685:     Vector res = v;
 686:     if (res == null)
 687:       res = new Vector();
 688:     else
 689:       res.clear();
 690: 
 691:     int endOffset = offset + length;
 692: 
 693:     int index1 = Collections.binarySearch(positions,
 694:                                           new GapContentPosition(offset),
 695:                                           new WeakPositionComparator());
 696:     if (index1 < 0)
 697:       index1 = -(index1 + 1);
 698: 
 699:     // Search the first index with the specified offset. The binarySearch does
 700:     // not necessarily find the first one.
 701:     while (index1 > 0)
 702:       {
 703:         WeakReference r = (WeakReference) positions.get(index1 - 1);
 704:         GapContentPosition p = (GapContentPosition) r.get();
 705:         if (p != null && p.mark == offset || p == null)
 706:           index1--;
 707:         else
 708:           break;
 709:       }
 710: 
 711:     for (ListIterator i = positions.listIterator(index1); i.hasNext();)
 712:       {
 713:         WeakReference r = (WeakReference) i.next();
 714:         GapContentPosition p = (GapContentPosition) r.get();
 715:         if (p == null)
 716:           continue;
 717: 
 718:         if (p.mark > endOffset)
 719:           break;
 720:         if (p.mark >= offset && p.mark <= endOffset)
 721:           res.add(p);
 722:       }
 723:     return res;
 724:   }
 725:   
 726:   /**
 727:    * Sets the mark of all <code>Position</code>s that are in the range 
 728:    * specified by <code>offset</code> and </code>length</code> within 
 729:    * the buffer array to <code>value</code>
 730:    *
 731:    * @param offset the start offset of the range to search
 732:    * @param length the length of the range to search
 733:    * @param value the new value for each mark
 734:    */
 735:   private void setPositionsInRange(int offset, int length, int value)
 736:   {
 737:     int endOffset = offset + length;
 738: 
 739:     int index1 = Collections.binarySearch(positions,
 740:                                           new GapContentPosition(offset),
 741:                                           new WeakPositionComparator());
 742:     if (index1 < 0)
 743:       index1 = -(index1 + 1);
 744: 
 745:     // Search the first index with the specified offset. The binarySearch does
 746:     // not necessarily find the first one.
 747:     while (index1 > 0)
 748:       {
 749:         WeakReference r = (WeakReference) positions.get(index1 - 1);
 750:         GapContentPosition p = (GapContentPosition) r.get();
 751:         if (p != null && p.mark == offset || p == null)
 752:           index1--;
 753:         else
 754:           break;
 755:       }
 756: 
 757:     for (ListIterator i = positions.listIterator(index1); i.hasNext();)
 758:       {
 759:         WeakReference r = (WeakReference) i.next();
 760:         GapContentPosition p = (GapContentPosition) r.get();
 761:         if (p == null)
 762:           continue;
 763: 
 764:         if (p.mark > endOffset)
 765:           break;
 766:         
 767:         if (p.mark >= offset && p.mark <= endOffset)
 768:           p.mark = value;
 769:       }
 770:   }
 771:   
 772:   /**
 773:    * Adjusts the mark of all <code>Position</code>s that are in the range 
 774:    * specified by <code>offset</code> and </code>length</code> within 
 775:    * the buffer array by <code>increment</code>
 776:    *
 777:    * @param offset the start offset of the range to search
 778:    * @param length the length of the range to search
 779:    * @param incr the increment
 780:    */
 781:   private void adjustPositionsInRange(int offset, int length, int incr)
 782:   {
 783:     int endOffset = offset + length;
 784: 
 785:     int index1 = Collections.binarySearch(positions,
 786:                                           new GapContentPosition(offset),
 787:                                           new WeakPositionComparator());
 788:     if (index1 < 0)
 789:       index1 = -(index1 + 1);
 790: 
 791:     // Search the first index with the specified offset. The binarySearch does
 792:     // not necessarily find the first one.
 793:     while (index1 > 0)
 794:       {
 795:         WeakReference r = (WeakReference) positions.get(index1 - 1);
 796:         GapContentPosition p = (GapContentPosition) r.get();
 797:         if (p != null && p.mark == offset || p == null)
 798:           index1--;
 799:         else
 800:           break;
 801:       }
 802: 
 803:     for (ListIterator i = positions.listIterator(index1); i.hasNext();)
 804:       {
 805:         WeakReference r = (WeakReference) i.next();
 806:         GapContentPosition p = (GapContentPosition) r.get();
 807:         if (p == null)
 808:           continue;
 809: 
 810:         if (p.mark > endOffset)
 811:           break;
 812: 
 813:         if (p.mark >= offset && p.mark <= endOffset)
 814:           p.mark += incr;
 815:       }
 816:   }
 817: 
 818:   /**
 819:    * Resets all <code>Position</code> that have an offset of <code>0</code>,
 820:    * to also have an array index of <code>0</code>. This might be necessary
 821:    * after a call to <code>shiftGap(0)</code>, since then the marks at offset
 822:    * <code>0</code> get shifted to <code>gapEnd</code>.
 823:    */
 824:   protected void resetMarksAtZero()
 825:   {
 826:     if (gapStart != 0)
 827:       return;
 828: 
 829:     setPositionsInRange(gapEnd, 0, 0);
 830:   }
 831: 
 832:   /**
 833:    * @specnote This method is not very well specified and the positions vector
 834:    *           is implementation specific. The undo positions are managed
 835:    *           differently in this implementation, this method is only here
 836:    *           for binary compatibility.
 837:    */
 838:   protected void updateUndoPositions(Vector positions, int offset, int length)
 839:   {
 840:     // We do nothing here.
 841:   }
 842: 
 843:   /**
 844:    * Outputs debugging info to System.err. It prints out the buffer array,
 845:    * the gapStart is marked by a &lt; sign, the gapEnd is marked by a &gt;
 846:    * sign and each position is marked by a # sign.
 847:    */
 848:   private void dump()
 849:   {
 850:     System.err.println("GapContent debug information");
 851:     System.err.println("buffer length: " + buffer.length);
 852:     System.err.println("gap start: " + gapStart);
 853:     System.err.println("gap end: " + gapEnd);
 854:     for (int i = 0; i < buffer.length; i++)
 855:       {
 856:         if (i == gapStart)
 857:           System.err.print('<');
 858:         if (i == gapEnd)
 859:           System.err.print('>');
 860: 
 861:         if (!Character.isISOControl(buffer[i]))
 862:           System.err.print(buffer[i]);
 863:         else
 864:           System.err.print('.');
 865:       }
 866:     System.err.println();
 867:   }
 868: 
 869:   private void dumpPositions()
 870:   {
 871:     for (Iterator i = positions.iterator(); i.hasNext();)
 872:       {
 873:         WeakReference r = (WeakReference) i.next();
 874:         GapContentPosition pos = (GapContentPosition) r.get();
 875:         System.err.println("position at: " + pos.mark);
 876:       }
 877:   }
 878: 
 879:   /**
 880:    * Clears all GC'ed references in the positions array.
 881:    */
 882:   private void clearPositionReferences()
 883:   {
 884:     Iterator i = positions.iterator();
 885:     while (i.hasNext())
 886:       {
 887:         WeakReference r = (WeakReference) i.next();
 888:         if (r.get() == null)
 889:           i.remove();
 890:       }
 891:   }
 892: }