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 java.io.File;
0017: import java.io.FileReader;
0018: import java.io.IOException;
0019: import java.io.Reader;
0020: import java.io.Writer;
0021:
0022: import javax.swing.text.BadLocationException;
0023:
0024: /**
0025: * Various text analyzes over the document
0026: *
0027: * @author Miloslav Metelka
0028: * @version 1.00
0029: */
0030:
0031: public class Analyzer {
0032:
0033: /** Platform default line separator */
0034: private static Object platformLS;
0035:
0036: /** Empty char array */
0037: public static final char[] EMPTY_CHAR_ARRAY = new char[0];
0038:
0039: /** Buffer filled by spaces used for spaces filling and tabs expansion */
0040: private static char spacesBuffer[] = new char[] { ' ' };
0041:
0042: /** Buffer filled by tabs used for tabs filling */
0043: private static char tabsBuffer[] = new char[] { '\t' };
0044:
0045: /** Cache up to 50 spaces strings */
0046: private static final int MAX_CACHED_SPACES_STRING_LENGTH = 50;
0047:
0048: /** Spaces strings cache. */
0049: private static final String[] spacesStrings = new String[MAX_CACHED_SPACES_STRING_LENGTH + 1];
0050:
0051: static {
0052: spacesStrings[0] = "";
0053: spacesStrings[MAX_CACHED_SPACES_STRING_LENGTH] = new String(
0054: getSpacesBuffer(MAX_CACHED_SPACES_STRING_LENGTH), 0,
0055: MAX_CACHED_SPACES_STRING_LENGTH);
0056: }
0057:
0058: private Analyzer() {
0059: // no instantiation
0060: }
0061:
0062: /** Get platform default line separator */
0063: public static Object getPlatformLS() {
0064: if (platformLS == null) {
0065: platformLS = System.getProperty("line.separator"); // NOI18N
0066: }
0067: return platformLS;
0068: }
0069:
0070: /**
0071: * Test line separator on given semgment. This implementation simply checks
0072: * the first line of file but it can be redefined to do more thorough test.
0073: *
0074: * @param seg
0075: * segment where analyzes are performed
0076: * @return line separator type found in the file
0077: */
0078: public static String testLS(char chars[], int len) {
0079: for (int i = 0; i < len; i++) {
0080: switch (chars[i]) {
0081: case '\r':
0082: if (i + 1 < len && chars[i + 1] == '\n') {
0083: return BaseDocument.LS_CRLF;
0084: } else {
0085: return BaseDocument.LS_CR;
0086: }
0087:
0088: case '\n':
0089: return BaseDocument.LS_LF;
0090: }
0091: }
0092: return null; // signal unspecified line separator
0093: }
0094:
0095: /**
0096: * Convert text with generic line separators to line feeds (LF). As the
0097: * linefeeds are one char long there is no need to allocate another buffer
0098: * since the only possibility is that the returned length will be smaller
0099: * than previous (if there were some CRLF separators.
0100: *
0101: * @param chars
0102: * char array with data to convert
0103: * @param len
0104: * valid portion of chars array
0105: * @return new valid portion of chars array after conversion
0106: */
0107: public static int convertLSToLF(char chars[], int len) {
0108: int tgtOffset = 0;
0109: short lsLen = 0; // length of separator found
0110: int moveStart = 0; // start of block that must be moved
0111: int moveLen; // length of data moved back in buffer
0112:
0113: for (int i = 0; i < len; i++) {
0114: // first of all - there's no need to handle single '\n'
0115: if (chars[i] == '\r') { // '\r' found
0116: if (i + 1 < len && chars[i + 1] == '\n') { // '\n' follows
0117: lsLen = 2; // '\r\n'
0118: } else {
0119: lsLen = 1; // only '\r'
0120: }
0121: }
0122:
0123: if (lsLen > 0) {
0124: moveLen = i - moveStart;
0125: if (moveLen > 0) {
0126: if (tgtOffset != moveStart) { // will need to arraycopy
0127: System.arraycopy(chars, moveStart, chars,
0128: tgtOffset, moveLen);
0129: }
0130: tgtOffset += moveLen;
0131: }
0132: chars[tgtOffset++] = '\n';
0133: moveStart += moveLen + lsLen; // skip separator
0134: i += lsLen - 1; // possibly skip '\n'
0135: lsLen = 0; // signal no separator found
0136: }
0137: }
0138:
0139: // now move the rest if it's necessary
0140: moveLen = len - moveStart;
0141: if (moveLen > 0) {
0142: if (tgtOffset != moveStart) {
0143: System.arraycopy(chars, moveStart, chars, tgtOffset,
0144: moveLen);
0145: }
0146: tgtOffset += moveLen;
0147: }
0148:
0149: return tgtOffset; // return current length
0150: }
0151:
0152: /**
0153: * Convert string with generic line separators to line feeds (LF).
0154: *
0155: * @param text
0156: * string to convert
0157: * @return new string with converted LSs to LFs
0158: */
0159: public static String convertLSToLF(String text) {
0160: char[] tgtChars = null;
0161: int tgtOffset = 0;
0162: short lsLen = 0; // length of separator found
0163: int moveStart = 0; // start of block that must be moved
0164: int moveLen; // length of data moved back in buffer
0165: int textLen = text.length();
0166:
0167: for (int i = 0; i < textLen; i++) {
0168: // first of all - there's no need to handle single '\n'
0169: if (text.charAt(i) == '\r') { // '\r' found
0170: if (i + 1 < textLen && text.charAt(i + 1) == '\n') { // '\n'
0171: // follows
0172: lsLen = 2; // '\r\n'
0173: } else {
0174: lsLen = 1; // only '\r'
0175: }
0176: }
0177:
0178: if (lsLen > 0) {
0179: if (tgtChars == null) {
0180: tgtChars = new char[textLen];
0181: text.getChars(0, textLen, tgtChars, 0); // copy whole array
0182: }
0183: moveLen = i - moveStart;
0184: if (moveLen > 0) {
0185: if (tgtOffset != moveStart) { // will need to arraycopy
0186: text.getChars(moveStart, moveStart + moveLen,
0187: tgtChars, tgtOffset);
0188: }
0189: tgtOffset += moveLen;
0190: }
0191: tgtChars[tgtOffset++] = '\n';
0192: moveStart += moveLen + lsLen; // skip separator
0193: i += lsLen - 1; // possibly skip '\n'
0194: lsLen = 0; // signal no separator found
0195: }
0196: }
0197:
0198: // now move the rest if it's necessary
0199: moveLen = textLen - moveStart;
0200: if (moveLen > 0) {
0201: if (tgtOffset != moveStart) {
0202: text.getChars(moveStart, moveStart + moveLen, tgtChars,
0203: tgtOffset);
0204: }
0205: tgtOffset += moveLen;
0206: }
0207:
0208: return (tgtChars == null) ? text : new String(tgtChars, 0,
0209: tgtOffset);
0210: }
0211:
0212: public static boolean isSpace(String s) {
0213: int len = s.length();
0214: for (int i = 0; i < len; i++) {
0215: if (s.charAt(i) != ' ') {
0216: return false;
0217: }
0218: }
0219: return true;
0220: }
0221:
0222: /** Return true if the array contains only space chars */
0223: public static boolean isSpace(char[] chars, int offset, int len) {
0224: while (len > 0) {
0225: if (chars[offset++] != ' ') {
0226: return false;
0227: }
0228: len--;
0229: }
0230: return true;
0231: }
0232:
0233: /** Return true if the array contains only space or tab chars */
0234: public static boolean isWhitespace(char[] chars, int offset, int len) {
0235: while (len > 0) {
0236: if (!Character.isWhitespace(chars[offset])) {
0237: return false;
0238: }
0239: offset++;
0240: len--;
0241: }
0242: return true;
0243: }
0244:
0245: /** Return the first index that is not space */
0246: public static int findFirstNonTab(char[] chars, int offset, int len) {
0247: while (len > 0) {
0248: if (chars[offset] != '\t') {
0249: return offset;
0250: }
0251: offset++;
0252: len--;
0253: }
0254: return -1;
0255: }
0256:
0257: /** Return the first index that is not space */
0258: public static int findFirstNonSpace(char[] chars, int offset,
0259: int len) {
0260: while (len > 0) {
0261: if (chars[offset] != ' ') {
0262: return offset;
0263: }
0264: offset++;
0265: len--;
0266: }
0267: return -1;
0268: }
0269:
0270: /** Return the first index that is not space or tab or new-line char */
0271: public static int findFirstNonWhite(char[] chars, int offset,
0272: int len) {
0273: while (len > 0) {
0274: if (!Character.isWhitespace(chars[offset])) {
0275: return offset;
0276: }
0277: offset++;
0278: len--;
0279: }
0280: return -1;
0281: }
0282:
0283: /** Return the last index that is not space or tab or new-line char */
0284: public static int findLastNonWhite(char[] chars, int offset, int len) {
0285: int i = offset + len - 1;
0286: while (i >= offset) {
0287: if (!Character.isWhitespace(chars[i])) {
0288: return i;
0289: }
0290: i--;
0291: }
0292: return -1;
0293: }
0294:
0295: /**
0296: * Count the number of line feeds in char array.
0297: *
0298: * @return number of LF characters contained in array.
0299: */
0300: public static int getLFCount(char chars[]) {
0301: return getLFCount(chars, 0, chars.length);
0302: }
0303:
0304: public static int getLFCount(char chars[], int offset, int len) {
0305: int lfCount = 0;
0306: while (len > 0) {
0307: if (chars[offset++] == '\n') {
0308: lfCount++;
0309: }
0310: len--;
0311: }
0312: return lfCount;
0313: }
0314:
0315: public static int getLFCount(String s) {
0316: int lfCount = 0;
0317: int len = s.length();
0318: for (int i = 0; i < len; i++) {
0319: if (s.charAt(i) == '\n') {
0320: lfCount++;
0321: }
0322: }
0323: return lfCount;
0324: }
0325:
0326: public static int findFirstLFOffset(char[] chars, int offset,
0327: int len) {
0328: while (len > 0) {
0329: if (chars[offset++] == '\n') {
0330: return offset - 1;
0331: }
0332: len--;
0333: }
0334: return -1;
0335: }
0336:
0337: public static int findFirstLFOffset(String s) {
0338: int len = s.length();
0339: for (int i = 0; i < len; i++) {
0340: if (s.charAt(i) == '\n') {
0341: return i;
0342: }
0343: }
0344: return -1;
0345: }
0346:
0347: public static int findFirstTab(char[] chars, int offset, int len) {
0348: while (len > 0) {
0349: if (chars[offset++] == '\t') {
0350: return offset - 1;
0351: }
0352: len--;
0353: }
0354: return -1;
0355: }
0356:
0357: public static int findFirstTabOrLF(char[] chars, int offset, int len) {
0358: while (len > 0) {
0359: switch (chars[offset++]) {
0360: case '\t':
0361: case '\n':
0362: return offset - 1;
0363: }
0364: len--;
0365: }
0366: return -1;
0367: }
0368:
0369: /**
0370: * Reverses the order of characters in the array. It works from the begining
0371: * of the array, so no offset is given.
0372: */
0373: public static void reverse(char[] chars, int len) {
0374: for (int i = ((--len - 1) >> 1); i >= 0; --i) {
0375: char ch = chars[i];
0376: chars[i] = chars[len - i];
0377: chars[len - i] = ch;
0378: }
0379: }
0380:
0381: public static boolean equals(String s, char[] chars) {
0382: return equals(s, chars, 0, chars.length);
0383: }
0384:
0385: public static boolean equals(String s, char[] chars, int offset,
0386: int len) {
0387: if (s.length() != len) {
0388: return false;
0389: }
0390: for (int i = 0; i < len; i++) {
0391: if (s.charAt(i) != chars[offset + i]) {
0392: return false;
0393: }
0394: }
0395: return true;
0396: }
0397:
0398: /**
0399: * Do initial reading of document. Translate any line separators found in
0400: * document to line separators used by document. It also cares for elements
0401: * that were already created on the empty document. Although the document
0402: * must be empty there can be already marks created. Initial read is
0403: * equivalent to inserting the string array of the whole document size at
0404: * position 0 in the document. Therefore all the marks that are not
0405: * insertAfter are removed and reinserted to the end of the document after
0406: * the whole initial read is finished.
0407: *
0408: * @param doc
0409: * document for which the initialization is performed
0410: * @param reader
0411: * reader from which document should be read
0412: * @param lsType
0413: * line separator type
0414: * @param testLS
0415: * test line separator of file and if it's consistent, use it
0416: * @param markDistance
0417: * the distance between the new syntax mark is put
0418: */
0419: public static void initialRead(BaseDocument doc, Reader reader,
0420: boolean testLS) throws IOException {
0421: // document MUST be empty
0422: if (doc.getLength() > 0) {
0423: return;
0424: }
0425:
0426: // for valid reader read the document
0427: if (reader != null) {
0428: // Size of the read buffer
0429: int readBufferSize = ((Integer) doc
0430: .getProperty(SettingsNames.READ_BUFFER_SIZE))
0431: .intValue();
0432: // Default distance between marks
0433: int markDistance = ((Integer) doc
0434: .getProperty(SettingsNames.READ_MARK_DISTANCE))
0435: .intValue();
0436:
0437: /* buffer into which the data from file will be read */
0438: char readBuf[] = new char[readBufferSize];
0439: boolean firstRead = true; // first cycle of reading from stream
0440: boolean lastCR = false; // Last character in the previous buffer was
0441: // '\r'
0442: int readLen = 0; // How many chars was read within cycle
0443: int bufLen = 0; // Length of readBuf[] used area
0444: int bufStartPos = 0; // Starting position of the buffer in
0445: // document
0446: int lineOffset = 0; // Line counter
0447: int lineLimit = 0; // Longest line found
0448: int lineStartOffset = 0; // Start offset of the last line
0449: int nextMarkOffset = markDistance; // Buffer offset of the next
0450: // mark insertion
0451: int nextMarkPos = markDistance; // Position where the next mark
0452: // should be inserted
0453: int markCount = 0; // Total mark count
0454:
0455: synchronized (doc.op) {
0456: // array for getting mark array from renderer inner class
0457: final Mark origMarks[][] = new Mark[1][];
0458: doc.op.renderMarks(new DocMarks.Renderer() {
0459: public void render() {
0460: origMarks[0] = copyAllMarks();
0461: }
0462: });
0463:
0464: // now remove all the marks that are not insert after
0465: for (int i = 0; i < origMarks[0].length; i++) {
0466: Mark mark = origMarks[0][i];
0467: if (!(mark.getInsertAfter() || (mark instanceof MarkFactory.CaretMark))) {
0468: try {
0469: mark.remove();
0470: } catch (InvalidMarkException e) {
0471: if (Boolean
0472: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0473: e.printStackTrace();
0474: }
0475: }
0476: }
0477: }
0478:
0479: // Enter the loop where all data from file will be read
0480: while (true) {
0481:
0482: // Read part of document into buffer
0483: readLen = 0;
0484: while (readLen == 0) { // read non-zero chars for algorithm
0485: // to work
0486: readLen = reader.read(readBuf, 0,
0487: readBuf.length);
0488: }
0489:
0490: // Determine starting offset
0491: int moveStart = 0;
0492: if (lastCR) {
0493: if (readLen > 0 && readBuf[0] == '\n') {
0494: moveStart++; // force to go past the initial '\n'
0495: }
0496: }
0497:
0498: // check readLen value
0499: if (readLen == -1) { // no more characters
0500: break;
0501: }
0502:
0503: // check if we need to scan buffer for LS
0504: if (firstRead && testLS) {
0505: String newLS = testLS(readBuf, readLen);
0506: if (newLS != null) {
0507: doc
0508: .putProperty(
0509: BaseDocument.READ_LINE_SEPARATOR_PROP,
0510: newLS);
0511: // if
0512: // (doc.getProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP)
0513: // == null) {
0514: // doc.putProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP,
0515: // newLS);
0516: // }
0517: // The property above is left empty so the write()
0518: // will default to the READ_LINE_SEPARATOR_PROP
0519: }
0520: }
0521:
0522: // now handle whole buffer - do syntax analyzes and create
0523: // marks
0524: int tgtOffset = 0;
0525: int offset;
0526: // Cycle through all chars in the buffer
0527: for (offset = 0; offset < readLen; offset++) {
0528:
0529: // Check whether the mark should be inserted
0530: if (offset == nextMarkOffset) {
0531: MarkFactory.SyntaxMark mark = new MarkFactory.SyntaxMark();
0532: try {
0533: doc.op.insertMark(mark, nextMarkPos,
0534: lineOffset);
0535: } catch (BadLocationException e) {
0536: if (Boolean
0537: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0538: e.printStackTrace();
0539: }
0540: } catch (InvalidMarkException e) {
0541: if (Boolean
0542: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0543: e.printStackTrace();
0544: }
0545: }
0546:
0547: markCount++;
0548: nextMarkOffset += markDistance;
0549: nextMarkPos += markDistance;
0550: }
0551:
0552: switch (readBuf[offset]) {
0553: case '\n':
0554: if (lastCR) { // ignore this LF
0555: if (moveStart > 0) { // move the last line
0556: int moveLen = offset - moveStart;
0557: if (moveLen > 0) { // handles initial '\n'
0558: // when this is -1
0559: System.arraycopy(readBuf,
0560: moveStart, readBuf,
0561: tgtOffset, moveLen);
0562: tgtOffset += moveLen;
0563: }
0564:
0565: } else { // chars yet start from the begining of
0566: // buffer
0567: tgtOffset = offset;
0568: }
0569: moveStart = offset + 1;
0570: lastCR = false;
0571: lineStartOffset++;
0572: nextMarkOffset++; // correct the removal of
0573: // the '\n'
0574:
0575: } else { // no CR before this LF
0576: lineOffset++;
0577: lineLimit = Math.max(lineLimit, offset
0578: - lineStartOffset);
0579: lineStartOffset = offset + 1;
0580: }
0581:
0582: break;
0583:
0584: case '\r':
0585: lastCR = true;
0586: readBuf[offset] = '\n';
0587: lineOffset++;
0588: lineLimit = Math.max(lineLimit, offset
0589: - lineStartOffset);
0590: lineStartOffset = offset + 1;
0591: break;
0592: }
0593:
0594: }
0595:
0596: if (moveStart > 0) {
0597: int moveLen = offset - moveStart;
0598: if (moveLen > 0) {
0599: System.arraycopy(readBuf, moveStart,
0600: readBuf, tgtOffset, moveLen);
0601: tgtOffset += moveLen;
0602: }
0603: } else {
0604: tgtOffset = offset; // no movings yet
0605: }
0606:
0607: nextMarkOffset -= offset;
0608: lineStartOffset -= offset;
0609:
0610: // store this buffer into cacheSupport
0611: try {
0612: doc.op.directCacheWrite(bufStartPos, readBuf,
0613: 0, tgtOffset);
0614: } catch (BadLocationException e) {
0615: if (Boolean
0616: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0617: e.printStackTrace();
0618: }
0619: }
0620:
0621: // initialize cache with first buffer
0622: if (firstRead) {
0623: doc.op.initCacheContent(readBuf, 0, tgtOffset);
0624: }
0625:
0626: bufStartPos += tgtOffset;
0627: firstRead = false;
0628: }
0629:
0630: // Now reinsert marks that were removed at begining to the end
0631: for (int i = 0; i < origMarks[0].length; i++) {
0632: Mark mark = origMarks[0][i];
0633: if (!(mark.getInsertAfter() || (mark instanceof MarkFactory.CaretMark))) {
0634: try {
0635: doc.op.insertMark(origMarks[0][i],
0636: bufStartPos, lineOffset);
0637: } catch (InvalidMarkException e) {
0638: if (Boolean
0639: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0640: e.printStackTrace();
0641: }
0642: } catch (BadLocationException e) {
0643: if (Boolean
0644: .getBoolean("netbeans.debug.exceptions")) { // NOI18N
0645: e.printStackTrace();
0646: }
0647: }
0648: }
0649: }
0650:
0651: // Set the line limit document property
0652: doc.putProperty(BaseDocument.LINE_LIMIT_PROP,
0653: new Integer(lineLimit));
0654:
0655: // Mark the inserted area is not yet covered by syntax
0656: // state-infos
0657: doc.op.initialReadUpdate(); // refresh the document stats
0658: }
0659: }
0660: }
0661:
0662: /** Read from some reader and insert into document */
0663: static void read(BaseDocument doc, Reader reader, int pos)
0664: throws BadLocationException, IOException {
0665: int lastCR = 0;
0666: int this CR;
0667: boolean lastRead = false;
0668: int readLen;
0669: int readBufferSize = ((Integer) doc
0670: .getProperty(SettingsNames.READ_BUFFER_SIZE))
0671: .intValue();
0672: char[] readBuf = new char[readBufferSize + 1];
0673: while (true) {
0674: // read part of document into buffer
0675: readLen = 0;
0676: while (readLen == 0) { // read non-zero chars for algorithm to work
0677: readLen = reader.read(readBuf, lastCR, readBufferSize);
0678: }
0679: this CR = 0;
0680: if (readLen == -1) {
0681: if (lastCR == 0) {
0682: break;
0683: } else {
0684: readLen = 0;
0685: }
0686: } else { // some chars were read
0687: if (readBuf[readLen + lastCR - 1] == '\r') {
0688: this CR = 1;
0689: readLen--;
0690: }
0691: }
0692: readLen += lastCR;
0693: readLen = convertLSToLF(readBuf, readLen);
0694: doc
0695: .insertString(pos, new String(readBuf, 0, readLen),
0696: null);
0697: pos += readLen;
0698: lastCR = this CR;
0699: }
0700: }
0701:
0702: /** Write from document to some writer */
0703: static void write(BaseDocument doc, Writer writer, int pos, int len)
0704: throws BadLocationException, IOException {
0705: String lsType = (String) doc
0706: .getProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP);
0707: if (lsType == null) {
0708: lsType = (String) doc
0709: .getProperty(BaseDocument.READ_LINE_SEPARATOR_PROP);
0710: if (lsType == null) {
0711: lsType = BaseDocument.LS_LF;
0712: }
0713: }
0714: int writeBufferSize = ((Integer) doc
0715: .getProperty(SettingsNames.WRITE_BUFFER_SIZE))
0716: .intValue();
0717: char[] getBuf = new char[writeBufferSize];
0718: char[] writeBuf = new char[2 * writeBufferSize];
0719: int actLen = 0;
0720:
0721: while (len > 0) {
0722: actLen = Math.min(len, writeBufferSize);
0723: doc.getChars(pos, getBuf, 0, actLen);
0724: int tgtLen = convertLFToLS(getBuf, actLen, writeBuf, lsType);
0725: writer.write(writeBuf, 0, tgtLen);
0726: pos += actLen;
0727: len -= actLen;
0728: }
0729:
0730: // Append new-line if not the last char
0731: /*
0732: * if (actLen > 0 && getBuf[actLen - 1] != '\n') { writer.write(new
0733: * char[] { '\n' }, 0, 1); }
0734: */
0735:
0736: }
0737:
0738: /** Get visual column. */
0739: public static int getColumn(char buffer[], int offset, int len,
0740: int tabSize, int startCol) {
0741: int col = startCol;
0742: int endOffset = offset + len;
0743:
0744: // Check wrong tab values
0745: if (tabSize <= 0) {
0746: new Exception("Wrong tab size=" + tabSize)
0747: .printStackTrace();
0748: tabSize = 8;
0749: }
0750:
0751: while (offset < endOffset) {
0752: switch (buffer[offset++]) {
0753: case '\t':
0754: col = (col + tabSize) / tabSize * tabSize;
0755: break;
0756: default:
0757: col++;
0758: }
0759: }
0760: return col;
0761: }
0762:
0763: /**
0764: * Get buffer filled with appropriate number of spaces. The buffer can have
0765: * actually more spaces than requested.
0766: *
0767: * @param numSpaces
0768: * number of spaces
0769: */
0770: public static synchronized char[] getSpacesBuffer(int numSpaces) {
0771: // check if there's enough space in white space array
0772: while (numSpaces > spacesBuffer.length) {
0773: char tmpBuf[] = new char[spacesBuffer.length * 2]; // new buffer
0774: System.arraycopy(spacesBuffer, 0, tmpBuf, 0,
0775: spacesBuffer.length);
0776: System.arraycopy(spacesBuffer, 0, tmpBuf,
0777: spacesBuffer.length, spacesBuffer.length);
0778: spacesBuffer = tmpBuf;
0779: }
0780:
0781: return spacesBuffer;
0782: }
0783:
0784: /**
0785: * Get string filled with space characters. There is optimization to return
0786: * the same string instance for up to ceratin number of spaces.
0787: *
0788: * @param numSpaces
0789: * number of spaces determining the resulting size of the string.
0790: */
0791: public static synchronized String getSpacesString(int numSpaces) {
0792: if (numSpaces <= MAX_CACHED_SPACES_STRING_LENGTH) { // Cached
0793: String ret = spacesStrings[numSpaces];
0794: if (ret == null) {
0795: ret = spacesStrings[MAX_CACHED_SPACES_STRING_LENGTH]
0796: .substring(0, numSpaces);
0797: spacesStrings[numSpaces] = ret;
0798: }
0799:
0800: return ret;
0801:
0802: } else { // non-cached
0803: return new String(getSpacesBuffer(numSpaces), 0, numSpaces);
0804: }
0805: }
0806:
0807: /**
0808: * Get buffer of the requested size filled entirely with space character.
0809: *
0810: * @param numSpaces
0811: * number of spaces in the returned character buffer.
0812: */
0813: public static char[] createSpacesBuffer(int numSpaces) {
0814: char[] ret = new char[numSpaces];
0815: System.arraycopy(getSpacesBuffer(numSpaces), 0, ret, 0,
0816: numSpaces);
0817: return ret;
0818: }
0819:
0820: /**
0821: * Get buffer filled with appropriate number of tabs. The buffer can have
0822: * actually more tabs than requested.
0823: *
0824: * @param numSpaces
0825: * number of spaces
0826: */
0827: public static char[] getTabsBuffer(int numTabs) {
0828: // check if there's enough space in white space array
0829: if (numTabs > tabsBuffer.length) {
0830: char tmpBuf[] = new char[numTabs * 2]; // new buffer
0831:
0832: // initialize new buffer with spaces
0833: for (int i = 0; i < tmpBuf.length; i += tabsBuffer.length) {
0834: System.arraycopy(tabsBuffer, 0, tmpBuf, i, Math.min(
0835: tabsBuffer.length, tmpBuf.length - i));
0836: }
0837: tabsBuffer = tmpBuf;
0838: }
0839:
0840: return tabsBuffer;
0841: }
0842:
0843: /**
0844: * Get the string that should be used for indentation of the given level.
0845: *
0846: * @param indent
0847: * indentation level
0848: * @param expandTabs
0849: * whether tabs should be expanded to spaces or not
0850: * @param tabSize
0851: * size substituted visually for the '\t' character
0852: */
0853: public static String getIndentString(int indent,
0854: boolean expandTabs, int tabSize) {
0855: return getWhitespaceString(0, indent, expandTabs, tabSize);
0856: }
0857:
0858: /**
0859: * Get the string that should be used for indentation of the given level.
0860: *
0861: * @param indent
0862: * indentation level
0863: * @param expandTabs
0864: * whether tabs should be expanded to spaces or not
0865: * @param tabSize
0866: * size of the '\t' character
0867: */
0868: public static String getWhitespaceString(int startCol, int endCol,
0869: boolean expandTabs, int tabSize) {
0870: return (expandTabs || tabSize <= 0) ? getSpacesString(endCol
0871: - startCol) : new String(createWhiteSpaceFillBuffer(
0872: startCol, endCol, tabSize));
0873: }
0874:
0875: /**
0876: * createWhitespaceFillBuffer() with the non-capital 's' should be used.
0877: *
0878: * @deprecated
0879: */
0880: public static char[] createWhiteSpaceFillBuffer(int startCol,
0881: int endCol, int tabSize) {
0882: return createWhitespaceFillBuffer(startCol, endCol, tabSize);
0883: }
0884:
0885: /**
0886: * Get buffer filled with spaces/tabs so that it reaches from some column to
0887: * some other column.
0888: *
0889: * @param startCol
0890: * starting visual column of the whitespace on the line
0891: * @param endCol
0892: * ending visual column of the whitespace on the line
0893: * @param tabSize
0894: * size substituted visually for the '\t' character
0895: */
0896: public static char[] createWhitespaceFillBuffer(int startCol,
0897: int endCol, int tabSize) {
0898: if (startCol >= endCol) {
0899: return EMPTY_CHAR_ARRAY;
0900: }
0901:
0902: // Check wrong tab values
0903: if (tabSize <= 0) {
0904: new Exception("Wrong tab size=" + tabSize)
0905: .printStackTrace();
0906: tabSize = 8;
0907: }
0908:
0909: int tabs = 0;
0910: int spaces = 0;
0911: int nextTab = (startCol + tabSize) / tabSize * tabSize;
0912: if (nextTab > endCol) { // only spaces
0913: spaces += endCol - startCol;
0914: } else { // at least one tab
0915: tabs++; // jump to first tab
0916: int endSpaces = endCol - endCol / tabSize * tabSize;
0917: tabs += (endCol - endSpaces - nextTab) / tabSize;
0918: spaces += endSpaces;
0919: }
0920:
0921: char[] ret = new char[tabs + spaces];
0922: if (tabs > 0) {
0923: System.arraycopy(getTabsBuffer(tabs), 0, ret, 0, tabs);
0924: }
0925: if (spaces > 0) {
0926: System.arraycopy(getSpacesBuffer(spaces), 0, ret, tabs,
0927: spaces);
0928: }
0929: return ret;
0930: }
0931:
0932: /**
0933: * Loads the file and performs conversion of line separators to LF. This
0934: * method can be used in debuging of syntax scanner or somewhere else.
0935: *
0936: * @param fileName
0937: * the name of the file to load
0938: * @return array of loaded characters with '\n' as line separator
0939: */
0940: public static char[] loadFile(String fileName) throws IOException {
0941: File file = new File(fileName);
0942: char chars[] = new char[(int) file.length()];
0943: FileReader reader = new FileReader(file);
0944: reader.read(chars);
0945: reader.close();
0946: int len = Analyzer.convertLSToLF(chars, chars.length);
0947: if (len != chars.length) {
0948: char copyChars[] = new char[len];
0949: System.arraycopy(chars, 0, copyChars, 0, len);
0950: chars = copyChars;
0951: }
0952: return chars;
0953: }
0954:
0955: /**
0956: * Convert text with LF line separators to text that uses line separators of
0957: * the document. This function is used when saving text into the file.
0958: * Segment's data are converted inside the segment's data or new segment's
0959: * data array is allocated. NOTE: Source segment must have just LFs as
0960: * separators! Otherwise the conversion won't work correctly.
0961: *
0962: * @param src
0963: * source chars to convert from
0964: * @param len
0965: * length of valid part of src data
0966: * @param tgt
0967: * target chars to convert to. The array MUST have twice the size
0968: * of src otherwise index exception can be thrown
0969: * @param lsType
0970: * line separator type to be used i.e. LS_LF, LS_CR, LS_CRLF
0971: * @return length of valid chars in tgt array
0972: */
0973: public static int convertLFToLS(char[] src, int len, char[] tgt,
0974: String lsType) {
0975: if (lsType.equals(BaseDocument.LS_CR)) { // CR instead of LF
0976: System.arraycopy(src, 0, tgt, 0, len);
0977:
0978: // now do conversion for LS_CR
0979: if (lsType == BaseDocument.LS_CR) { // will convert '\n' to '\r'
0980: char chars[] = tgt;
0981: for (int i = 0; i < len; i++) {
0982: if (chars[i] == '\n') {
0983: chars[i] = '\r';
0984: }
0985: }
0986: }
0987: return len;
0988: } else if (lsType.equals(BaseDocument.LS_CRLF)) {
0989: int tgtLen = 0;
0990: int moveStart = 0; // start of block that must be moved
0991: int moveLen; // length of chars moved
0992:
0993: for (int i = 0; i < len; i++) {
0994: if (src[i] == '\n') { // '\n' found
0995: moveLen = i - moveStart;
0996: if (moveLen > 0) { // will need to arraycopy
0997: System.arraycopy(src, moveStart, tgt, tgtLen,
0998: moveLen);
0999: tgtLen += moveLen;
1000: }
1001: tgt[tgtLen++] = '\r';
1002: tgt[tgtLen++] = '\n';
1003: moveStart = i + 1; // skip separator
1004: }
1005: }
1006:
1007: // now move the rest if it's necessary
1008: moveLen = len - moveStart;
1009: if (moveLen > 0) {
1010: System.arraycopy(src, moveStart, tgt, tgtLen, moveLen);
1011: tgtLen += moveLen;
1012: }
1013: return tgtLen;
1014: } else { // Using either \n or line separator is unknown
1015: System.arraycopy(src, 0, tgt, 0, len);
1016: return len;
1017: }
1018: }
1019:
1020: public static boolean startsWith(char[] chars, char[] prefix) {
1021: if (chars == null || chars.length < prefix.length) {
1022: return false;
1023: }
1024: for (int i = 0; i < prefix.length; i++) {
1025: if (chars[i] != prefix[i]) {
1026: return false;
1027: }
1028: }
1029: return true;
1030: }
1031:
1032: public static boolean endsWith(char[] chars, char[] suffix) {
1033: if (chars == null || chars.length < suffix.length) {
1034: return false;
1035: }
1036: for (int i = chars.length - suffix.length; i < chars.length; i++) {
1037: if (chars[i] != suffix[i]) {
1038: return false;
1039: }
1040: }
1041: return true;
1042: }
1043:
1044: public static char[] concat(char[] chars1, char[] chars2) {
1045: if (chars1 == null || chars1.length == 0) {
1046: return chars2;
1047: }
1048: if (chars2 == null || chars2.length == 0) {
1049: return chars1;
1050: }
1051: char[] ret = new char[chars1.length + chars2.length];
1052: System.arraycopy(chars1, 0, ret, 0, chars1.length);
1053: System.arraycopy(chars2, 0, ret, chars1.length, chars2.length);
1054: return ret;
1055: }
1056:
1057: public static char[] extract(char[] chars, int offset, int len) {
1058: char[] ret = new char[len];
1059: System.arraycopy(chars, offset, ret, 0, len);
1060: return ret;
1061: }
1062:
1063: public static boolean blocksHit(int[] blocks, int startPos,
1064: int endPos) {
1065: return (blocksIndex(blocks, startPos, endPos) >= 0);
1066: }
1067:
1068: public static int blocksIndex(int[] blocks, int startPos, int endPos) {
1069: if (blocks.length > 0) {
1070: int onlyEven = ~1;
1071: int low = 0;
1072: int high = blocks.length - 2;
1073:
1074: while (low <= high) {
1075: int mid = ((low + high) / 2) & onlyEven;
1076:
1077: if (blocks[mid + 1] <= startPos) {
1078: low = mid + 2;
1079: } else if (blocks[mid] >= endPos) {
1080: high = mid - 2;
1081: } else {
1082: return low; // found
1083: }
1084: }
1085: }
1086:
1087: return -1;
1088: }
1089:
1090: /**
1091: * Remove all spaces from the given string.
1092: *
1093: * @param s
1094: * original string
1095: * @return string with all spaces removed
1096: */
1097: public static String removeSpaces(String s) {
1098: int spcInd = s.indexOf(' ');
1099: if (spcInd >= 0) {
1100: StringBuffer sb = new StringBuffer(s.substring(0, spcInd));
1101: int sLen = s.length();
1102: for (int i = spcInd + 1; i < sLen; i++) {
1103: char ch = s.charAt(i);
1104: if (ch != ' ') {
1105: sb.append(ch);
1106: }
1107: }
1108: return sb.toString();
1109: }
1110: return s;
1111: }
1112:
1113: }
|