GNU Classpath (0.18) | ||
Frames | No Frames |
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: }
GNU Classpath (0.18) |