Source for javax.swing.text.GapContent

   1: /* GapContent.java --
   2:    Copyright (C) 2002, 2004, 2005 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.util.Collections;
  43: import java.util.LinkedList;
  44: import java.util.ListIterator;
  45: 
  46: import javax.swing.undo.UndoableEdit;
  47: 
  48: /**
  49:  * This implementation of {@link AbstractDocument.Content} uses a gapped buffer.
  50:  * This takes advantage of the fact that text area content is mostly inserted
  51:  * sequentially. The buffer is a char array that maintains a gap at the current
  52:  * insertion point. If characters a inserted at gap boundaries, the cost is
  53:  * minimal (simple array access). The array only has to be shifted around when
  54:  * the insertion point moves (then the gap also moves and one array copy is
  55:  * necessary) or when the gap is filled up and the buffer has to be enlarged.
  56:  * 
  57:  * TODO: Implement UndoableEdit support stuff
  58:  */
  59: public class GapContent
  60:     implements AbstractDocument.Content, Serializable
  61: {
  62: 
  63:   /**
  64:    * A {@link Position} implementation for <code>GapContent</code>.
  65:    */
  66:   class GapContentPosition
  67:       implements Position, Comparable
  68:   {
  69: 
  70:     /** The index within the buffer array. */
  71:     int mark;
  72: 
  73:     /**
  74:      * Creates a new GapContentPosition object.
  75:      * 
  76:      * @param mark the mark of this Position
  77:      */
  78:     GapContentPosition(int mark)
  79:     {
  80:       this.mark = mark;
  81:     }
  82: 
  83:     /**
  84:      * Comparable interface implementation. This is used to store all
  85:      * positions in an ordered fashion.
  86:      * 
  87:      * @param o the object to be compared to this
  88:      * 
  89:      * @return a negative integer if this is less than <code>o</code>, zero
  90:      *         if both are equal or a positive integer if this is greater than
  91:      *         <code>o</code>
  92:      * 
  93:      * @throws ClassCastException if <code>o</code> is not a
  94:      *         GapContentPosition or Integer object
  95:      */
  96:     public int compareTo(Object o)
  97:     {
  98:       if (o instanceof Integer)
  99:       {
 100:         int otherMark = ((Integer) o).intValue();
 101:         return mark - otherMark;
 102:       }
 103:       else
 104:       {
 105:         GapContentPosition other = (GapContentPosition) o;
 106:         return mark - other.mark;
 107:       }
 108:     }
 109: 
 110:     /**
 111:      * Returns the current offset of this Position within the content.
 112:      * 
 113:      * @return the current offset of this Position within the content.
 114:      */
 115:     public int getOffset()
 116:     {
 117:       if (mark <= gapStart)
 118:         return mark;
 119:       else
 120:         return mark - (gapEnd - gapStart);
 121:     }
 122:   }
 123: 
 124:   private static final long serialVersionUID = 8374645204155842629L;
 125: 
 126:   /**
 127:    * This is the default buffer size and the amount of bytes that a buffer is
 128:    * extended if it is full.
 129:    */
 130:   static final int DEFAULT_BUFSIZE = 64;
 131: 
 132:   /**
 133:    * The text buffer.
 134:    */
 135:   char[] buffer;
 136: 
 137:   /**
 138:    * The index of the first character of the gap.
 139:    */
 140:   int gapStart;
 141: 
 142:   /**
 143:    * The index of the character after the last character of the gap.
 144:    */
 145:   int gapEnd;
 146: 
 147:   /**
 148:    * The positions generated by this GapContent. They are kept in an ordered
 149:    * fashion, so they can be looked up easily.
 150:    */
 151:   LinkedList positions;
 152: 
 153:   /**
 154:    * Creates a new GapContent object.
 155:    */
 156:   public GapContent()
 157:   {
 158:     this(DEFAULT_BUFSIZE);
 159:   }
 160: 
 161:   /**
 162:    * Creates a new GapContent object with a specified initial size.
 163:    * 
 164:    * @param size the initial size of the buffer
 165:    */
 166:   public GapContent(int size)
 167:   {
 168:     buffer = (char[]) allocateArray(size);
 169:     gapStart = 0;
 170:     gapEnd = size - 1;
 171:     buffer[size - 1] = '\n';
 172:     positions = new LinkedList();
 173:   }
 174: 
 175:   /**
 176:    * Allocates an array of the specified length that can then be used as
 177:    * buffer.
 178:    * 
 179:    * @param size the size of the array to be allocated
 180:    * 
 181:    * @return the allocated array
 182:    */
 183:   protected Object allocateArray(int size)
 184:   {
 185:     return new char[size];
 186:   }
 187: 
 188:   /**
 189:    * Returns the length of the allocated buffer array.
 190:    * 
 191:    * @return the length of the allocated buffer array
 192:    */
 193:   protected int getArrayLength()
 194:   {
 195:     return buffer.length;
 196:   }
 197: 
 198:   /**
 199:    * Returns the length of the content.
 200:    * 
 201:    * @return the length of the content
 202:    */
 203:   public int length()
 204:   {
 205:     return buffer.length - (gapEnd - gapStart);
 206:   }
 207: 
 208:   /**
 209:    * Inserts a string at the specified position.
 210:    * 
 211:    * @param where the position where the string is inserted
 212:    * @param str the string that is to be inserted
 213:    * 
 214:    * @return an UndoableEdit object (currently not supported, so
 215:    *         <code>null</code> is returned)
 216:    * 
 217:    * @throws BadLocationException if <code>where</code> is not a valid
 218:    *         location in the buffer
 219:    */
 220:   public UndoableEdit insertString(int where, String str)
 221:       throws BadLocationException
 222:   {
 223:     // check arguments
 224:     int length = length();
 225:     int strLen = str.length();
 226: 
 227:     if (where >= length)
 228:       throw new BadLocationException("the where argument cannot be greater"
 229:           + " than the content length", where);
 230: 
 231:     replace(where, 0, str.toCharArray(), str.length());
 232: 
 233:     return null;
 234:   }
 235: 
 236:   /**
 237:    * Removes a piece of content at th specified position.
 238:    * 
 239:    * @param where the position where the content is to be removed
 240:    * @param nitems number of characters to be removed
 241:    * 
 242:    * @return an UndoableEdit object (currently not supported, so
 243:    *         <code>null</code> is returned)
 244:    * 
 245:    * @throws BadLocationException if <code>where</code> is not a valid
 246:    *         location in the buffer
 247:    */
 248:   public UndoableEdit remove(int where, int nitems) throws BadLocationException
 249:   {
 250:     // check arguments
 251:     int length = length();
 252: 
 253:     if (where >= length)
 254:       throw new BadLocationException("the where argument cannot be greater"
 255:           + " than the content length", where);
 256:     if ((where + nitems) > length)
 257:       throw new BadLocationException("where + nitems cannot be greater"
 258:           + " than the content length", where + nitems);
 259: 
 260:     replace(where, nitems, null, 0);
 261: 
 262:     return null;
 263:   }
 264: 
 265:   /**
 266:    * Returns a piece of content as String.
 267:    * 
 268:    * @param where the start location of the fragment
 269:    * @param len the length of the fragment
 270:    * 
 271:    * @throws BadLocationException if <code>where</code> or
 272:    *         <code>where + len</code> are no valid locations in the buffer
 273:    */
 274:   public String getString(int where, int len) throws BadLocationException
 275:   {
 276:     Segment seg = new Segment();
 277:     try
 278:       {
 279:         getChars(where, len, seg);
 280:         return new String(seg.array, seg.offset, seg.count);
 281:       }
 282:     catch (StringIndexOutOfBoundsException ex)
 283:       {
 284:         int invalid = 0;
 285:         if (seg.offset < 0 || seg.offset >= seg.array.length)
 286:           invalid = seg.offset;
 287:         else
 288:           invalid = seg.offset + seg.count;
 289:         throw new BadLocationException("Illegal location: array.length = "
 290:                                        + seg.array.length + ", offset = "
 291:                                        + seg.offset + ", count = "
 292:                                        + seg.count, invalid);
 293:       }
 294:   }
 295: 
 296:   /**
 297:    * Fetches a piece of content and stores it in a {@link Segment} object.
 298:    * 
 299:    * If the requested piece of text spans the gap, the content is copied into a
 300:    * new array. If it doesn't then it is contiguous and the actual content
 301:    * store is returned.
 302:    * 
 303:    * @param where the start location of the fragment
 304:    * @param len the length of the fragment
 305:    * @param txt the Segment object to store the fragment in
 306:    * 
 307:    * @throws BadLocationException if <code>where</code> or
 308:    *         <code>where + len</code> are no valid locations in the buffer
 309:    */
 310:   public void getChars(int where, int len, Segment txt)
 311:       throws BadLocationException
 312:   {
 313:     // check arguments
 314:     int length = length();
 315:     if (where >= length)
 316:       throw new BadLocationException("the where argument cannot be greater"
 317:           + " than the content length", where);
 318:     if ((where + len) > length)
 319:       throw new BadLocationException("len plus where cannot be greater"
 320:           + " than the content length", len + where);
 321: 
 322:     // check if requested segment is contiguous
 323:     if ((where < gapStart) && ((gapStart - where) < len))
 324:     {
 325:       // requested segment is not contiguous -> copy the pieces together
 326:       char[] copy = new char[len];
 327:       int lenFirst = gapStart - where; // the length of the first segment
 328:       System.arraycopy(buffer, where, copy, 0, lenFirst);
 329:       System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst);
 330:       txt.array = copy;
 331:       txt.offset = 0;
 332:       txt.count = len;
 333:     }
 334:     else
 335:     {
 336:       // requested segment is contiguous -> we can simply return the
 337:       // actual content
 338:       txt.array = buffer;
 339:       if (where < gapStart)
 340:         txt.offset = where;
 341:       else
 342:         txt.offset = where + (gapEnd - gapStart);
 343:       txt.count = len;
 344:     }
 345:   }
 346: 
 347:   /**
 348:    * Creates and returns a mark at the specified position.
 349:    * 
 350:    * @param offset the position at which to create the mark
 351:    * 
 352:    * @return the create Position object for the mark
 353:    * 
 354:    * @throws BadLocationException if the offset is not a valid position in the
 355:    *         buffer
 356:    */
 357:   public Position createPosition(final int offset) throws BadLocationException
 358:   {
 359:     if (offset < 0 || offset > length())
 360:       throw new BadLocationException("The offset was out of the bounds of this"
 361:           + " buffer", offset);
 362: 
 363:     // We store the actual array index in the GapContentPosition. The real
 364:     // offset is then calculated in the GapContentPosition.
 365:     int mark = offset;
 366:     if (offset > gapStart)
 367:       mark += gapEnd - gapStart;
 368:     GapContentPosition pos = new GapContentPosition(mark);
 369: 
 370:     // Add this into our list in a sorted fashion.
 371:     int index = Collections.binarySearch(positions, pos);
 372:     if (index < 0)
 373:       index = -(index + 1);
 374:     positions.add(index, pos);
 375: 
 376:     return pos;
 377:   }
 378: 
 379:   /**
 380:    * Enlarges the gap. This allocates a new bigger buffer array, copy the
 381:    * segment before the gap as it is and the segment after the gap at the end
 382:    * of the new buffer array. This does change the gapEnd mark but not the
 383:    * gapStart mark.
 384:    * 
 385:    * @param newSize the new size of the gap
 386:    */
 387:   protected void shiftEnd(int newSize)
 388:   {
 389:     int delta = (gapEnd - gapStart) - newSize;
 390:     char[] newBuf = (char[]) allocateArray(length() + newSize);
 391:     System.arraycopy(buffer, 0, newBuf, 0, gapStart);
 392:     System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length
 393:         - gapEnd);
 394:     gapEnd = gapStart + newSize;
 395:     buffer = newBuf;
 396: 
 397:     // Update the marks after the gapEnd.
 398:     int index = Collections.binarySearch(positions, new GapContentPosition(
 399:         gapEnd));
 400:     if (index < 0)
 401:     {
 402:       index = -(index + 1);
 403:     }
 404:     for (ListIterator i = positions.listIterator(index); i.hasNext();)
 405:     {
 406:       GapContentPosition p = (GapContentPosition) i.next();
 407:       p.mark += delta;
 408:     }
 409:   }
 410: 
 411:   /**
 412:    * Shifts the gap to the specified position.
 413:    * 
 414:    * @param newGapStart the new start position of the gap
 415:    */
 416:   protected void shiftGap(int newGapStart)
 417:   {
 418:     int newGapEnd = newGapStart + (gapEnd - gapStart);
 419: 
 420:     // Update the positions between newGapEnd and (old) gapEnd. The marks
 421:     // must be shifted by (gapEnd - newGapEnd).
 422:     int index1 = Collections.binarySearch(positions,
 423:                                           new GapContentPosition(gapEnd));
 424:     int index2 = Collections.binarySearch(positions,
 425:                                           new GapContentPosition(newGapEnd));
 426:     if (index1 > 0 && index2 > 0)
 427:       {
 428:         int i1 = Math.min(index1, index2);
 429:         int i2 = Math.max(index1, index2);
 430:         for (ListIterator i = positions.listIterator(i1); i.hasNext();)
 431:           {
 432:             if (i.nextIndex() > i2)
 433:               break;
 434:             
 435:             GapContentPosition p = (GapContentPosition) i.next();
 436:             p.mark += gapEnd - newGapEnd;
 437:           }
 438:       }
 439: 
 440:     if (newGapStart == gapStart)
 441:       return;
 442:     else if (newGapStart < gapStart)
 443:       {
 444:         System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart
 445:                          - newGapStart);
 446:         gapStart = newGapStart;
 447:         gapEnd = newGapEnd;
 448:       }
 449:     else
 450:       {
 451:         System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart
 452:                          - gapStart);
 453:         gapStart = newGapStart;
 454:         gapEnd = newGapEnd;
 455:       }
 456:   }
 457: 
 458:   /**
 459:    * Returns the allocated buffer array.
 460:    * 
 461:    * @return the allocated buffer array
 462:    */
 463:   protected Object getArray()
 464:   {
 465:     return buffer;
 466:   }
 467: 
 468:   /**
 469:    * Replaces a portion of the storage with the specified items.
 470:    * 
 471:    * @param position the position at which to remove items
 472:    * @param rmSize the number of items to remove
 473:    * @param addItems the items to add at location
 474:    * @param addSize the number of items to add
 475:    */
 476:   protected void replace(int position, int rmSize, Object addItems,
 477:                          int addSize)
 478:   {
 479:     // Remove content
 480:     shiftGap(position);
 481:     gapEnd += rmSize;
 482: 
 483:     // If gap is too small, enlarge the gap.
 484:     if ((gapEnd - gapStart) < addSize)
 485:       shiftEnd(addSize);
 486: 
 487:     // Add new items to the buffer.
 488:     if (addItems != null)
 489:       {
 490:         System.arraycopy(addItems, 0, buffer, gapStart, addSize);
 491:         gapStart += addSize;
 492:       }
 493:   }
 494: }