0001: /*
0002: * Sun Public License Notice
0003: *
0004: * The contents of this file are subject to the Sun Public License
0005: * Version 1.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://www.sun.com/
0008: *
0009: * The Original Code is NetBeans. The Initial Developer of the Original
0010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
0011: * Microsystems, Inc. All Rights Reserved.
0012: */
0013:
0014: package org.netbeans.editor;
0015:
0016: import javax.swing.text.BadLocationException;
0017:
0018: /**
0019: * Document cache The cache is used to perform insert/remove/read/find
0020: * operations over the document. The document data are partly stored in cache
0021: * fragments and partly in cache support. Cache can contain several
0022: * non-overlapping fragments. At all times there is one fragment called default
0023: * fragment. It's used for all operations of callers that don't pass valid
0024: * fragment information to the insert/remove and other methods.
0025: *
0026: * @author Miloslav Metelka
0027: * @version 1.00
0028: */
0029:
0030: class DocCache {
0031:
0032: /**
0033: * Old contents of the fragment will be reused if backward overlapping is at
0034: * least this len
0035: */
0036: private static final int MIN_FRAGMENT_BACK_OVERLAP_LEN = 256;
0037:
0038: /**
0039: * CacheSupport that this cache uses. Cache support holds the whole
0040: * document. There are two obvious storages - char array held in memory and
0041: * file based storage. When there are changes made to the cache, they are
0042: * held in fragments of the cache until they become so big that they must be
0043: * flushed to the support.
0044: */
0045: private DocCacheSupport support;
0046:
0047: /** Array of all fragments that this cache uses */
0048: private Fragment[] frags;
0049:
0050: /**
0051: * Default fragment. It is used when null is passed as segment to
0052: * insert/remove and other operations. It's also used when docLen is 0 to be
0053: * the only one fragment available for insertion.
0054: */
0055: private Fragment defFrag;
0056:
0057: /**
0058: * Direct mode allowing to forward all operations directly to cache support.
0059: * This is useful when support is memory based. No cache fragments are
0060: * created for direct mode to save memory.
0061: */
0062: private boolean directMode;
0063:
0064: /**
0065: * Document length difference of all the fragments against the length of
0066: * document that is given by support document len. Formula: support document
0067: * len + docLenDelta = total document len
0068: */
0069: private int docLenDelta = 0;
0070:
0071: /* Statistics */
0072: public int statRead = 0;
0073: public int statInsert = 0;
0074: public int statRemove = 0;
0075: public int statReadFragCnt = 0;
0076: public int statWriteFragCnt = 0;
0077: public int statOverlapCnt = 0;
0078: public int statBackOverlapCnt = 0;
0079: public int statFragSwitchCnt = 0;
0080: public int statFragSetEmpty = 0;
0081:
0082: /**
0083: * Create the new cache with the specified size of default fragment.
0084: *
0085: * @param support
0086: * CacheSupport to use for the document
0087: * @param len
0088: * Length of the default fragment
0089: * @param directMode
0090: * whether all operations should be routed to support
0091: */
0092: public DocCache(DocCacheSupport support, int len, boolean directMode) {
0093: this .support = support;
0094: if (directMode && !support.supportsDirectMode()) {
0095: directMode = false;
0096: }
0097: this .directMode = directMode;
0098: if (!directMode) {
0099: defFrag = addFragment(len);
0100: }
0101: }
0102:
0103: /**
0104: * Set initial content of the cache. This function may be called only once,
0105: * after construction, before the data in cache are read or manipulated.
0106: * Otherwise the cache content will be damaged. The reason for using this
0107: * function is that content that's read from reader and that should be put
0108: * into support can be also put directly into cache which saves whole
0109: * support read.
0110: *
0111: * @param initCache
0112: * initial cache data
0113: * @param offset
0114: * first valid offset in initial cache
0115: * @param cacheLen
0116: * length of data initial cache
0117: */
0118: void initCacheContent(char initCache[], int offset, int cacheLen) {
0119: if (directMode) {
0120: return;
0121: }
0122: if (initCache != null) {
0123: defFrag.fragLen = Math.min(defFrag.buffer.length, cacheLen
0124: - offset);
0125: defFrag.origLen = defFrag.fragLen;
0126: System.arraycopy(initCache, offset, defFrag.buffer, 0,
0127: defFrag.fragLen);
0128: defFrag.startPos = 0;
0129: }
0130: }
0131:
0132: public synchronized Fragment addFragment(int fragLen) {
0133: if (directMode) {
0134: return null;
0135: }
0136: Fragment f = new Fragment(fragLen);
0137: if (frags != null) {
0138: Fragment[] tmpFrags = new Fragment[frags.length + 1];
0139: System.arraycopy(frags, 0, tmpFrags, 0, frags.length);
0140: tmpFrags[frags.length] = f;
0141: frags = tmpFrags;
0142: } else {
0143: frags = new Fragment[1];
0144: frags[0] = f;
0145: }
0146: return f;
0147: }
0148:
0149: /** Flush the whole cache to support */
0150: public synchronized void flush() {
0151: if (directMode) {
0152: return;
0153: }
0154: for (int i = 0; i < frags.length; i++) {
0155: if (frags[i].valid) {
0156: frags[i].write();
0157: }
0158: }
0159: }
0160:
0161: /**
0162: * Read fragment and ensure that specified position will be inside (or at
0163: * the end) of the fragment's cache.
0164: *
0165: * @param frag
0166: * fragment to read
0167: * @param pos
0168: * position that must be inside the fragment
0169: */
0170: private void readFrag(Fragment frag, int pos) {
0171: int len; // len of chars to be read
0172: Fragment f;
0173:
0174: if (frag.modified) {
0175: frag.write(); // flush this fragment if modified
0176: }
0177:
0178: int mantLow = 0, mantHigh = getDocLenImpl(); // mantinels
0179: int csDelta = 0;
0180: int i;
0181: for (i = 0; i < frags.length; i++) {
0182: f = frags[i];
0183: if (f.valid && f != frag) {
0184: if (f.startPos > pos) {
0185: mantHigh = f.startPos;
0186: break;
0187: } else {
0188: csDelta += f.origLen - f.fragLen;
0189: mantLow = f.startPos + f.fragLen;
0190: }
0191: }
0192: }
0193:
0194: // count position from which should be read
0195: pos = Math.max(pos - frag.buffer.length / 2, mantLow);
0196: len = Math.min(mantHigh - pos, frag.buffer.length);
0197: frag.read(pos, len, csDelta);
0198:
0199: // repair the fragment's position in frags
0200: if (i == frags.length
0201: || (frags[i] != frag && (i == 0 || frags[i - 1] != frag))) {
0202: statFragSwitchCnt++;
0203: int fragInd;
0204: for (fragInd = 0; fragInd < frags.length; fragInd++) {
0205: if (frags[fragInd] == frag) {
0206: break;
0207: }
0208: }
0209: if (fragInd < i) { // frag before i
0210: for (int j = fragInd + 1; j < i; j++) {
0211: frags[j - 1] = frags[j];
0212: }
0213: frags[i - 1] = frag;
0214: } else { // frag after i
0215: for (int j = fragInd; j > i; j--) {
0216: frags[j] = frags[j - 1];
0217: }
0218: frags[i] = frag;
0219: }
0220: }
0221: frag.valid = true;
0222: }
0223:
0224: /**
0225: * Get the correct fragment for a given position. Search all the fragments
0226: * if there's one that contains given position. If not get or create
0227: * optional fragment and read into it.
0228: *
0229: * @param pos
0230: * position that the fragment should contain
0231: * @param frag
0232: * current fragment
0233: * @param wantInsert
0234: * want to perform insert on got fragment
0235: * @return fragment that will contain position
0236: */
0237: private Fragment getFrag(int pos, Fragment frag, boolean wantInsert) {
0238: Fragment f;
0239: for (int i = 0; i < frags.length; i++) {
0240: f = frags[i];
0241: if (f.valid) {
0242: if (wantInsert) {
0243: if (f.isIn(pos)) {
0244: if (pos == f.startPos + f.fragLen) { // right at the
0245: // end of frag
0246: i++;
0247: while (i < frags.length) {
0248: if (frags[i].valid) {
0249: if (frags[i].startPos == pos) { // successive
0250: // frag
0251: f = frags[i];
0252: break;
0253: }
0254: }
0255: i++;
0256: }
0257: }
0258: // test if fragment full for insertions
0259: if (f.modified && f.lastMod == f.buffer.length) {
0260: readFrag(f, pos);
0261: }
0262: return f;
0263: }
0264: } else {
0265: if (f.isInside(pos)) {
0266: return f;
0267: }
0268: }
0269: }
0270: }
0271:
0272: // check for zero document len
0273: if (getDocLenImpl() == 0) { // should be just for inserts
0274: for (int i = 0; i < frags.length; i++) {
0275: f = frags[i];
0276: if (f.valid) {
0277: return f; // in fact there should be any valid
0278: }
0279: }
0280: // no valid fragment, make defFrag active
0281: defFrag.setEmptyValid();
0282: return defFrag;
0283: }
0284:
0285: // get the fragment and read into it
0286: if (frag != null) {
0287: f = frag.actFrag = frag;
0288: } else {
0289: f = defFrag;
0290: }
0291: readFrag(f, pos);
0292: return f;
0293: }
0294:
0295: /**
0296: * Update starting positions of all fragments that have higher start
0297: * positions than the given one
0298: *
0299: * @param frag
0300: * fragment that will not be updated
0301: * @param pos
0302: * position over which the fragments will be updated
0303: * @param delta
0304: * how much increase/decrease starting positions
0305: */
0306: private void updateStartPos(Fragment frag, int pos, int delta) {
0307: for (int i = frags.length - 1; i >= 0; i--) {
0308: Fragment f = frags[i];
0309: if (f.valid) {
0310: if (f.startPos >= pos) {
0311: if (f != frag) {
0312: f.startPos += delta;
0313: }
0314: } else {
0315: break; // reached lowest position
0316: }
0317: }
0318: }
0319: }
0320:
0321: /**
0322: * Insert the array of chars at some position. It can be done in one or more
0323: * cycles.
0324: *
0325: * @param pos
0326: * from which position
0327: * @param text
0328: * text to insert
0329: * @param frag
0330: * dedicated fragment (can be null)
0331: */
0332: public synchronized void insert(int pos, char text[], Fragment frag)
0333: throws BadLocationException {
0334: if (pos < 0 || pos > getDocLenImpl()) {
0335: throwPosException(pos);
0336: }
0337: int restLen = text.length; // rest of len to insert
0338: if (restLen == 0) {
0339: return;
0340: }
0341: if (directMode) {
0342: support.insert(pos, text, 0, restLen);
0343: return;
0344: }
0345: int wrLen; // write len in one cycle
0346: int bufPos; // relative position from the start of cache
0347: int moveLen; // how much data will be moved inside cache to make
0348: // space
0349: Fragment f = (frag == null) ? defFrag : frag.actFrag;
0350: boolean bufOK = false;
0351: if (f.isInside(pos)
0352: && (!f.modified || f.lastMod < f.buffer.length)) {
0353: bufOK = true; // pos inside and buffer not full for insertion
0354: }
0355:
0356: while (restLen > 0) { // till all the data will be inserted
0357: // is pos in current fragment
0358: if (bufOK) {
0359: bufOK = false;
0360: } else {
0361: f = getFrag(pos, frag, true); // get fragment for inserting
0362: }
0363: bufPos = pos - f.startPos;
0364: if (!f.modified) {
0365: f.modified = true;
0366: f.firstMod = f.lastMod = bufPos;
0367: }
0368: wrLen = f.buffer.length - Math.max(bufPos, f.lastMod);
0369: wrLen = Math.min(wrLen, restLen);
0370: // find how many bytes should be moved inside the cache
0371: moveLen = Math.min(f.fragLen - bufPos, f.buffer.length
0372: - (bufPos + wrLen));
0373: if (moveLen > 0) { // now make space for inserted data
0374: System.arraycopy(f.buffer, bufPos, f.buffer, bufPos
0375: + wrLen, moveLen);
0376: }
0377: // move the data from text array to the cache
0378: System.arraycopy(text, text.length - restLen, f.buffer,
0379: bufPos, wrLen);
0380: f.updatePos(bufPos, wrLen); // correct positions in the fragment
0381: docLenDelta += wrLen;
0382: if (frags.length > 1) {
0383: updateStartPos(f, pos, wrLen); // correct positions of other
0384: // frags
0385: }
0386: restLen -= wrLen;
0387: pos += wrLen;
0388: }
0389:
0390: if (frag != null) {
0391: frag.actFrag = f;
0392: }
0393: statInsert += text.length; // update insert statistics
0394: }
0395:
0396: /**
0397: * Insert the string at some position. It can be done in one or more cycles.
0398: *
0399: * @param pos
0400: * from which position
0401: * @param text
0402: * text to insert
0403: * @param frag
0404: * dedicated fragment (can be null)
0405: */
0406: public synchronized void insertString(int pos, String text,
0407: Fragment frag) throws BadLocationException {
0408: if (pos < 0 || pos > getDocLenImpl()) {
0409: throwPosException(pos);
0410: }
0411: int textLen = text.length();
0412: int restLen = textLen; // rest of len to insert
0413: if (restLen == 0) {
0414: return;
0415: }
0416: if (directMode) {
0417: support.insertString(pos, text, 0, restLen);
0418: return;
0419: }
0420: int wrLen; // write len in one cycle
0421: int bufPos; // relative position from the start of cache
0422: int moveLen; // how much data will be moved inside cache to make
0423: // space
0424: Fragment f = (frag == null) ? defFrag : frag.actFrag;
0425: boolean bufOK = false;
0426: if (f.isInside(pos)
0427: && (!f.modified || f.lastMod < f.buffer.length)) {
0428: bufOK = true; // pos inside and buffer not full for insertion
0429: }
0430:
0431: while (restLen > 0) { // till all the data will be inserted
0432: // is pos in current fragment
0433: if (bufOK) {
0434: bufOK = false;
0435: } else {
0436: f = getFrag(pos, frag, true); // get fragment for inserting
0437: }
0438: bufPos = pos - f.startPos;
0439: if (!f.modified) {
0440: f.modified = true;
0441: f.firstMod = f.lastMod = bufPos;
0442: }
0443: wrLen = f.buffer.length - Math.max(bufPos, f.lastMod);
0444: wrLen = Math.min(wrLen, restLen);
0445: // find how many bytes should be moved inside the cache
0446: moveLen = Math.min(f.fragLen - bufPos, f.buffer.length
0447: - (bufPos + wrLen));
0448: if (moveLen > 0) { // now make space for inserted data
0449: System.arraycopy(f.buffer, bufPos, f.buffer, bufPos
0450: + wrLen, moveLen);
0451: }
0452: // move the data from text to the cache
0453: text.getChars(textLen - restLen, textLen - restLen + wrLen,
0454: f.buffer, bufPos);
0455: f.updatePos(bufPos, wrLen); // correct positions in the fragment
0456: docLenDelta += wrLen;
0457: if (frags.length > 1) {
0458: updateStartPos(f, pos, wrLen); // correct positions of other
0459: // frags
0460: }
0461: restLen -= wrLen;
0462: pos += wrLen;
0463: }
0464:
0465: if (frag != null) {
0466: frag.actFrag = f;
0467: }
0468: statInsert += textLen; // update insert statistics
0469: }
0470:
0471: /**
0472: * Remove the specified count of chars from the specified position.
0473: *
0474: * @param pos
0475: * position from which remove the text
0476: * @param len
0477: * length of the data to be removed
0478: * @param frag
0479: * dedicated fragment (can be null)
0480: * @returns the removed text
0481: */
0482: public synchronized void remove(int pos, int len, Fragment frag)
0483: throws BadLocationException {
0484: int removeLen; // remove length inside the cache
0485: int restLen = len; // remaining len to remove
0486: int bufPos;
0487: if (len == 0) {
0488: return;
0489: }
0490: if (pos < 0) {
0491: throwPosException(pos);
0492: }
0493: if (pos + len > getDocLenImpl()) {
0494: throwPosException(pos + len);
0495: }
0496: if (directMode) {
0497: support.remove(pos, len);
0498: return;
0499: }
0500:
0501: // get the fragment
0502: Fragment f = (frag == null) ? defFrag : frag.actFrag;
0503: // first check if the removed block is fully in the cache
0504: if (pos < 0 || pos + len > f.startPos + f.fragLen) {
0505: // test position correctness
0506: }
0507:
0508: while (restLen > 0) {
0509: // get the correct fragment
0510: if (!f.isInside(pos)) {
0511: f = getFrag(pos, f, false);
0512: }
0513: // get the positions
0514: bufPos = pos - f.startPos;
0515: removeLen = Math.min(restLen, f.fragLen - bufPos);
0516: System.arraycopy(f.buffer, bufPos + removeLen, f.buffer,
0517: bufPos, f.fragLen - (bufPos + removeLen));
0518: if (!f.modified) {
0519: f.modified = true;
0520: f.firstMod = f.lastMod = bufPos;
0521: }
0522: f.updatePos(bufPos, -removeLen);
0523: docLenDelta -= removeLen;
0524: if (frags.length > 1) {
0525: updateStartPos(f, pos, -removeLen); // correct positions of
0526: // other frags
0527: }
0528: restLen -= removeLen;
0529: }
0530:
0531: if (frag != null) {
0532: frag.actFrag = f;
0533: }
0534: statRemove += len;
0535: }
0536:
0537: /**
0538: * Read the data from the cache. Output array must be provided.
0539: *
0540: * @param pos
0541: * position from which read starts
0542: * @param ret
0543: * char array where the data should be put. There must be enough
0544: * space in array to hold len requested characters
0545: * @param offset
0546: * offset in return array where data will be written
0547: * @param len
0548: * len to read
0549: * @param frag
0550: * dedicated fragment (can be null)
0551: */
0552: public synchronized void read(int pos, char ret[], int offset,
0553: int len, Fragment frag) throws BadLocationException {
0554: if (pos < 0 || len < 0) {
0555: throwPosException(pos);
0556: }
0557: if (pos + len > getDocLenImpl()) {
0558: throwPosException(pos + len);
0559: }
0560: if (directMode) {
0561: support.read(pos, ret, offset, len);
0562: return;
0563: }
0564: int getLen, bufPos;
0565: int restLen = len; // the rest to retrieve
0566: // get the fragment
0567: Fragment f = (frag == null) ? defFrag : frag.actFrag;
0568: while (restLen > 0) {
0569: // check if pos is inside fragment
0570: if (!f.isInside(pos)) {
0571: f = getFrag(pos, frag, false);
0572: }
0573: bufPos = pos - f.startPos; // position in cache
0574: getLen = Math.min(f.fragLen - bufPos, restLen);
0575: // copy into return array
0576: System.arraycopy(f.buffer, bufPos, ret, len - restLen
0577: + offset, getLen);
0578: pos += getLen;
0579: restLen -= getLen;
0580: }
0581:
0582: if (frag != null) {
0583: frag.actFrag = f;
0584: }
0585: statRead += len;
0586: }
0587:
0588: /**
0589: * Read the data from the cache and return reults as char array.
0590: *
0591: * @param pos
0592: * position from which read starts
0593: * @param len
0594: * len to read
0595: * @param frag
0596: * dedicated fragment (can be null)
0597: * @return char array with data from document
0598: */
0599: public final char[] read(int pos, int len, Fragment frag)
0600: throws BadLocationException {
0601: if (len < 0) {
0602: throwPosException(pos);
0603: }
0604: char ret[] = new char[len];
0605: read(pos, ret, 0, len, frag);
0606: return ret;
0607: }
0608:
0609: /**
0610: * Find something in the cache using specified <CODE>Finder</CODE>. It
0611: * covers both forward and backward searches. To do backward search, specify
0612: * endPos < startPos. The following position intervals are searched: forward
0613: * search: <startPos, endPos) - from startPos to endPos - 1 backward search:
0614: * <endPos, startPos) - from startPos - 1 to endPos Both startPos and endPos
0615: * can be -1 to indicate end of document position
0616: *
0617: * @param finder
0618: * finder that will be used for searching
0619: * @param startPos
0620: * position from which search starts. For backward search this
0621: * value is greater or equal than <CODE>endPos</CODE>.
0622: * @param endPos
0623: * limit position of search area For backward search this value
0624: * is lower than <CODE>startPos</CODE>.
0625: * @param frag
0626: * dedicated fragment (can be null)
0627: * @return position where the found string begins or -1 if the text was not
0628: * found
0629: */
0630: public synchronized int find(Finder finder, int startPos,
0631: int endPos, Fragment frag) throws BadLocationException {
0632: int docLen = getDocLenImpl();
0633: if (startPos == -1) {
0634: startPos = docLen;
0635: }
0636: if (endPos == -1) {
0637: endPos = docLen;
0638: }
0639:
0640: // check bounds
0641: if (startPos < 0 || startPos > docLen) {
0642: throwPosException(startPos);
0643: }
0644: if (endPos < 0 || endPos > docLen) {
0645: throwPosException(endPos);
0646: }
0647:
0648: finder.reset(); // initialize finder
0649: if (startPos == endPos) { // immediate return for void search
0650: return -1;
0651: }
0652: boolean forward = (startPos < endPos);
0653: int pos = forward ? startPos : (startPos - 1);
0654:
0655: if (directMode) {
0656: while (true) {
0657: pos = finder.find(0, support.getDirectModeBuffer(),
0658: forward ? startPos : endPos, forward ? endPos
0659: : startPos, pos, endPos);
0660:
0661: if (finder.isFound()) {
0662: if (forward) {
0663: if (pos < startPos || pos > endPos) {
0664: return -1; // invalid position returned
0665: }
0666: } else { // searching backward
0667: if (pos < endPos || pos > startPos) {
0668: return -1; // invalid position returned
0669: }
0670: }
0671: return pos;
0672:
0673: } else { // not yet found
0674:
0675: // Check position correctness. It eliminates
0676: // also the equalities because the empty buffer
0677: // would be pzssed in these cases to the finder
0678: if (forward) { // searching forward
0679: if (pos < startPos || pos >= endPos) {
0680: return -1;
0681: }
0682: } else { // searching backward
0683: if (pos < endPos || pos >= startPos) {
0684: return -1; // not found
0685: }
0686: }
0687: }
0688: }
0689: }
0690: // get the fragment
0691: Fragment f = (frag == null) ? defFrag : frag.actFrag;
0692: while (true) {
0693: // check if pos is inside fragment
0694: if (!f.isInside(pos)) {
0695: f = getFrag(pos, frag, false);
0696: }
0697: int offset1 = Math.max(forward ? startPos : endPos,
0698: f.startPos)
0699: - f.startPos;
0700: int offset2 = Math.min(forward ? endPos : startPos,
0701: f.startPos + f.fragLen)
0702: - f.startPos;
0703:
0704: pos = finder.find(f.startPos, f.buffer, offset1, offset2,
0705: pos, endPos);
0706:
0707: // check found position correctness
0708: if (finder.isFound()) {
0709: if (forward) {
0710: if (pos < startPos || pos > endPos) {
0711: return -1; // invalid position returned
0712: }
0713: } else { // searching backward
0714: if (pos < endPos || pos > startPos) {
0715: return -1; // invalid position returned
0716: }
0717: }
0718: break;
0719:
0720: } else { // not yet found
0721: // Check position correctness. It eliminates
0722: // also the equalities because the empty buffer
0723: // would be pzssed in these cases to the finder
0724: if (forward) { // searching forward
0725: if (pos < startPos || pos >= endPos) {
0726: return -1;
0727: }
0728: } else { // searching backward
0729: if (pos < endPos || pos >= startPos) {
0730: return -1; // not found
0731: }
0732: }
0733: }
0734: }
0735:
0736: if (frag != null) {
0737: frag.actFrag = f;
0738: }
0739: return pos; // return found position
0740: }
0741:
0742: /** Get the total length of the document. */
0743: public synchronized final int getDocLength() {
0744: return getDocLenImpl();
0745: }
0746:
0747: private int getDocLenImpl() {
0748: return support.getDocLength() + docLenDelta;
0749: }
0750:
0751: /** Fragment of the cache */
0752: private class Fragment {
0753:
0754: /** buffer space of the fragment */
0755: char buffer[];
0756:
0757: /** Position of the first character cached in the fragment */
0758: int startPos = -1;
0759:
0760: /**
0761: * Number of chars in the buffer of this fragment. If this value is 0,
0762: * it means that the fragment is invalid.
0763: */
0764: int fragLen;
0765:
0766: /** Original len when the buffer was read */
0767: int origLen;
0768:
0769: /** First modified char in the buffer */
0770: int firstMod;
0771:
0772: /** Offset of last modified char int the buffer */
0773: int lastMod;
0774:
0775: /** Was the buffer modified or not */
0776: boolean modified;
0777:
0778: /**
0779: * Is this fragment valid? Fragment becomes invalid when it contains no
0780: * data.
0781: */
0782: boolean valid;
0783:
0784: /**
0785: * Actual fragment which is either this fragment or when the last search
0786: * didn't succeeded in this fragment, some other fragment.
0787: */
0788: Fragment actFrag;
0789:
0790: /**
0791: * Construct new fragment with specified length
0792: *
0793: * @param len
0794: * len of the constructed fragment
0795: */
0796: Fragment(int len) {
0797: buffer = new char[len];
0798: actFrag = this ;
0799: }
0800:
0801: /** Is the position inside this fragment? */
0802: boolean isInside(int pos) {
0803: return (pos >= startPos) && (pos < startPos + fragLen);
0804: }
0805:
0806: /** Is the position inside or at the end of this fragment? */
0807: boolean isIn(int pos) {
0808: return (pos >= startPos) && (pos <= startPos + fragLen);
0809: }
0810:
0811: /** Flush the fragment to support */
0812: void write() {
0813: if (!modified) {
0814: return;
0815: }
0816:
0817: int endModPos = startPos + lastMod; // last modification
0818: int writeLen;
0819:
0820: // count the support delta
0821: int csDelta = 0;
0822: for (int i = 0; i < frags.length; i++) {
0823: Fragment f = frags[i];
0824: if (f.valid) {
0825: if (f.startPos >= this .startPos) {
0826: break;
0827: }
0828: csDelta += f.origLen - f.fragLen;
0829: }
0830: }
0831:
0832: try {
0833: if (fragLen != origLen) { // will either enlarge or shrink
0834: // document
0835: int delta = fragLen - origLen;
0836: if (delta > 0) { // delta > 0; need to enlarge document
0837: support.insert(endModPos - delta + csDelta,
0838: buffer, lastMod - delta, delta);
0839: writeLen = lastMod - firstMod - delta;
0840: } else { // delta < 0; need to shrink the document
0841: support.remove(endModPos + csDelta, -delta);
0842: writeLen = lastMod - firstMod;
0843: }
0844: } else { // delta is zero i.e. length of the buffer is the same
0845: writeLen = lastMod - firstMod;
0846: }
0847: if (writeLen > 0) {
0848: try {
0849: support.write(startPos + firstMod + csDelta,
0850: buffer, firstMod, writeLen);
0851: } catch (BadLocationException e) {
0852: if (Boolean
0853: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0854: e.printStackTrace();
0855: }
0856: }
0857: }
0858: } catch (BadLocationException e) {
0859: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
0860: e.printStackTrace();
0861: }
0862: }
0863:
0864: docLenDelta += origLen - fragLen;
0865: origLen = fragLen;
0866: modified = false;
0867: statWriteFragCnt++;
0868: }
0869:
0870: /**
0871: * Read the fragment to contain specified position
0872: *
0873: * @param pos
0874: * start position
0875: * @param len
0876: * length to read
0877: * @param csDelta
0878: * delta by which the reading position must be corrected when
0879: * reading through support
0880: */
0881: void read(int pos, int len, int csDelta) {
0882: int overlap;
0883: if (pos < startPos) { // read position is lower than start of frag
0884: overlap = Math.min(pos + len - startPos, fragLen);
0885: if (overlap > MIN_FRAGMENT_BACK_OVERLAP_LEN) {
0886: statBackOverlapCnt++;
0887: System.arraycopy(buffer, 0, buffer, startPos - pos,
0888: overlap);
0889: try {
0890: support.read(pos + csDelta, buffer, 0, startPos
0891: - pos);
0892: } catch (BadLocationException e) {
0893: if (Boolean
0894: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0895: e.printStackTrace();
0896: }
0897: }
0898:
0899: overlap = startPos + overlap; // now overlap means pos of
0900: // end of block
0901: if (overlap < pos + len) {
0902: try {
0903: support.read(startPos + fragLen + csDelta,
0904: buffer, overlap - pos, pos + len
0905: - overlap);
0906: } catch (BadLocationException e) {
0907: if (Boolean
0908: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0909: e.printStackTrace();
0910: }
0911: }
0912: }
0913:
0914: } else { // no or small overlap
0915: try {
0916: support.read(pos + csDelta, buffer, 0, len);
0917: } catch (BadLocationException e) {
0918: if (Boolean
0919: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0920: e.printStackTrace();
0921: }
0922: }
0923: }
0924: } else { // pos >= startPos
0925: overlap = startPos + fragLen - pos;
0926: if (overlap > 0) { // here any overlap is fine !!!
0927: statOverlapCnt++;
0928: System.arraycopy(buffer, pos - startPos, buffer, 0,
0929: overlap);
0930: try {
0931: support.read(pos + overlap + csDelta, buffer,
0932: overlap, len - overlap);
0933: } catch (BadLocationException e) {
0934: if (Boolean
0935: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0936: e.printStackTrace();
0937: }
0938: }
0939: } else { // no overlap
0940: try {
0941: support.read(pos + csDelta, buffer, 0, len);
0942: } catch (BadLocationException e) {
0943: if (Boolean
0944: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0945: e.printStackTrace();
0946: }
0947: }
0948: }
0949: }
0950:
0951: startPos = pos;
0952: fragLen = origLen = len;
0953: valid = true;
0954: statReadFragCnt++;
0955: }
0956:
0957: /** Invalidate segment so it will no longer be used for operations */
0958: void invalidate() {
0959: write(); // flush the fragment
0960: valid = false;
0961: startPos = -1; // important for isIn() and others
0962: fragLen = origLen = 0;
0963: }
0964:
0965: /**
0966: * Set this segment as valid even when it has zero size. It is done only
0967: * when total doc len is 0.
0968: */
0969: void setEmptyValid() {
0970: statFragSetEmpty++;
0971: fragLen = origLen = 0;
0972: startPos = 0;
0973: valid = true;
0974: }
0975:
0976: /**
0977: * Update various positions in fragment after modification.
0978: *
0979: * @param bufPos
0980: * position in the buffer where the modification was done
0981: * @param delta
0982: * of the change
0983: */
0984: void updatePos(int bufPos, int delta) {
0985: if (bufPos < firstMod) {
0986: firstMod = bufPos;
0987: }
0988:
0989: if (delta > 0) { // insert was done
0990: if (bufPos > lastMod) {
0991: lastMod = bufPos + delta;
0992: } else {
0993: lastMod += delta;
0994: }
0995: fragLen += delta;
0996: if (fragLen > buffer.length) {
0997: origLen -= fragLen - buffer.length;
0998: fragLen = buffer.length;
0999: }
1000: } else { // remove was done
1001: if (bufPos - delta <= lastMod) { // if whole block below
1002: // lastMod
1003: lastMod += delta;
1004: } else {
1005: lastMod = bufPos;
1006: }
1007: fragLen += delta;
1008: if (fragLen == 0) { // empty fragment must be invalidated
1009: invalidate();
1010: }
1011: }
1012: }
1013:
1014: public String toString() {
1015: int i;
1016: for (i = 0; i < frags.length; i++) {
1017: if (frags[i] == this ) {
1018: break;
1019: }
1020: }
1021: return "Frag[" + i + "] valid=" + valid + ", startPos="
1022: + startPos // NOI18N
1023: + ", fragLen=" + fragLen + ", origLen=" + origLen // NOI18N
1024: + ", fMod=" + firstMod + ", lMod=" + lastMod // NOI18N
1025: + ", mod=" + modified; // NOI18N
1026: }
1027:
1028: }
1029:
1030: /** Throw position exception */
1031: final void throwPosException(int pos) throws BadLocationException {
1032: throw new BadLocationException("DocCache: Invalid offset "
1033: + pos // NOI18N
1034: + ". Document length is " + getDocLenImpl(), pos); // NOI18N
1035: }
1036:
1037: public String toString() {
1038: String ret = "support=" + support; // NOI18N
1039: if (directMode) {
1040: ret += ", Direct mode, no fragments\n"; // NOI18N
1041: } else {
1042: ret += ", fragment count=" + frags.length + "\n"; // NOI18N
1043: for (int i = 0; i < frags.length; i++) {
1044: ret += frags[i] + "\n"; // NOI18N
1045: }
1046: }
1047:
1048: ret += " getDocLength()=" + getDocLength() + ", docLenDelta="
1049: + docLenDelta // NOI18N
1050: + ", statRead=" + statRead + ", statInsert=" // NOI18N
1051: + statInsert + ", statRemove=" + statRemove // NOI18N
1052: + ", statReadFragCnt=" + statReadFragCnt // NOI18N
1053: + ", statWriteFragCnt=" + statWriteFragCnt // NOI18N
1054: + ", statOverlapCnt=" + statOverlapCnt // NOI18N
1055: + ", statBackOverlapCnt=" + statBackOverlapCnt // NOI18N
1056: + ", statFragSwitchCnt=" + statFragSwitchCnt // NOI18N
1057: + ", statFragSetEmpty=" + statFragSetEmpty; // NOI18N
1058:
1059: return ret;
1060: }
1061:
1062: }
|