001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor;
043:
044: import java.io.Reader;
045: import java.io.Writer;
046: import java.io.File;
047: import java.io.FileReader;
048: import java.io.IOException;
049:
050: import javax.swing.text.BadLocationException;
051: import javax.swing.text.Segment;
052:
053: /**
054: * Various text analyzes over the document
055: *
056: * @author Miloslav Metelka
057: * @version 1.00
058: */
059:
060: public class Analyzer {
061:
062: /** Platform default line separator */
063: private static Object platformLS;
064:
065: /** Empty char array */
066: public static final char[] EMPTY_CHAR_ARRAY = new char[0];
067:
068: /** Buffer filled by spaces used for spaces filling and tabs expansion */
069: private static char spacesBuffer[] = new char[] { ' ' };
070:
071: /** Buffer filled by tabs used for tabs filling */
072: private static char tabsBuffer[] = new char[] { '\t' };
073:
074: /** Cache up to 50 spaces strings */
075: private static final int MAX_CACHED_SPACES_STRING_LENGTH = 50;
076:
077: /** Spaces strings cache. */
078: private static final String[] spacesStrings = new String[MAX_CACHED_SPACES_STRING_LENGTH + 1];
079:
080: static {
081: spacesStrings[0] = "";
082: spacesStrings[MAX_CACHED_SPACES_STRING_LENGTH] = new String(
083: getSpacesBuffer(MAX_CACHED_SPACES_STRING_LENGTH), 0,
084: MAX_CACHED_SPACES_STRING_LENGTH);
085: }
086:
087: private Analyzer() {
088: // no instantiation
089: }
090:
091: /** Get platform default line separator */
092: public static Object getPlatformLS() {
093: if (platformLS == null) {
094: platformLS = System.getProperty("line.separator"); // NOI18N
095: }
096: return platformLS;
097: }
098:
099: /** Test line separator on given semgment. This implementation simply checks
100: * the first line of file but it can be redefined to do more thorough test.
101: * @param seg segment where analyzes are performed
102: * @return line separator type found in the file
103: */
104: public static String testLS(char chars[], int len) {
105: for (int i = 0; i < len; i++) {
106: switch (chars[i]) {
107: case '\r':
108: if (i + 1 < len && chars[i + 1] == '\n') {
109: return BaseDocument.LS_CRLF;
110: } else {
111: return BaseDocument.LS_CR;
112: }
113:
114: case '\n':
115: return BaseDocument.LS_LF;
116: }
117: }
118: return null; // signal unspecified line separator
119: }
120:
121: /** Convert text with generic line separators to line feeds (LF).
122: * As the linefeeds are one char long there is no need to allocate
123: * another buffer since the only possibility is that the returned
124: * length will be smaller than previous (if there were some CRLF separators.
125: * @param chars char array with data to convert
126: * @param len valid portion of chars array
127: * @return new valid portion of chars array after conversion
128: */
129: public static int convertLSToLF(char chars[], int len) {
130: int tgtOffset = 0;
131: short lsLen = 0; // length of separator found
132: int moveStart = 0; // start of block that must be moved
133: int moveLen; // length of data moved back in buffer
134:
135: for (int i = 0; i < len; i++) {
136: // first of all - there's no need to handle single '\n'
137: if (chars[i] == '\r') { // '\r' found
138: if (i + 1 < len && chars[i + 1] == '\n') { // '\n' follows
139: lsLen = 2; // '\r\n'
140: } else {
141: lsLen = 1; // only '\r'
142: }
143: } else if (chars[i] == LineSeparatorConversion.LS
144: || chars[i] == LineSeparatorConversion.PS) {
145: lsLen = 1;
146: }
147:
148: if (lsLen > 0) {
149: moveLen = i - moveStart;
150: if (moveLen > 0) {
151: if (tgtOffset != moveStart) { // will need to arraycopy
152: System.arraycopy(chars, moveStart, chars,
153: tgtOffset, moveLen);
154: }
155: tgtOffset += moveLen;
156: }
157: chars[tgtOffset++] = '\n';
158: moveStart += moveLen + lsLen; // skip separator
159: i += lsLen - 1; // possibly skip '\n'
160: lsLen = 0; // signal no separator found
161: }
162: }
163:
164: // now move the rest if it's necessary
165: moveLen = len - moveStart;
166: if (moveLen > 0) {
167: if (tgtOffset != moveStart) {
168: System.arraycopy(chars, moveStart, chars, tgtOffset,
169: moveLen);
170: }
171: tgtOffset += moveLen;
172: }
173:
174: return tgtOffset; // return current length
175: }
176:
177: /** Convert string with generic line separators to line feeds (LF).
178: * @param text string to convert
179: * @return new string with converted LSs to LFs
180: */
181: public static String convertLSToLF(String text) {
182: char[] tgtChars = null;
183: int tgtOffset = 0;
184: short lsLen = 0; // length of separator found
185: int moveStart = 0; // start of block that must be moved
186: int moveLen; // length of data moved back in buffer
187: int textLen = text.length();
188:
189: for (int i = 0; i < textLen; i++) {
190: // first of all - there's no need to handle single '\n'
191: if (text.charAt(i) == '\r') { // '\r' found
192: if (i + 1 < textLen && text.charAt(i + 1) == '\n') { // '\n' follows
193: lsLen = 2; // '\r\n'
194: } else {
195: lsLen = 1; // only '\r'
196: }
197: } else if (text.charAt(i) == LineSeparatorConversion.LS
198: || text.charAt(i) == LineSeparatorConversion.PS) {
199: lsLen = 1;
200: }
201:
202: if (lsLen > 0) {
203: if (tgtChars == null) {
204: tgtChars = new char[textLen];
205: text.getChars(0, textLen, tgtChars, 0); // copy whole array
206: }
207: moveLen = i - moveStart;
208: if (moveLen > 0) {
209: if (tgtOffset != moveStart) { // will need to arraycopy
210: text.getChars(moveStart, moveStart + moveLen,
211: tgtChars, tgtOffset);
212: }
213: tgtOffset += moveLen;
214: }
215: tgtChars[tgtOffset++] = '\n';
216: moveStart += moveLen + lsLen; // skip separator
217: i += lsLen - 1; // possibly skip '\n'
218: lsLen = 0; // signal no separator found
219: }
220: }
221:
222: // now move the rest if it's necessary
223: moveLen = textLen - moveStart;
224: if (moveLen > 0) {
225: if (tgtOffset != moveStart) {
226: text.getChars(moveStart, moveStart + moveLen, tgtChars,
227: tgtOffset);
228: }
229: tgtOffset += moveLen;
230: }
231:
232: return (tgtChars == null) ? text : new String(tgtChars, 0,
233: tgtOffset);
234: }
235:
236: public static boolean isSpace(String s) {
237: int len = s.length();
238: for (int i = 0; i < len; i++) {
239: if (s.charAt(i) != ' ') {
240: return false;
241: }
242: }
243: return true;
244: }
245:
246: /** Return true if the array contains only space chars */
247: public static boolean isSpace(char[] chars, int offset, int len) {
248: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
249: + "offset = " + offset //NOI18N
250: + ", len = " + len //NOI18N
251: + ", chars.length = " + chars.length; //NOI18N
252:
253: while (len > 0) {
254: if (chars[offset++] != ' ') { //NOI18N
255: return false;
256: }
257: len--;
258: }
259: return true;
260: }
261:
262: /** Return true if the array contains only space or tab chars */
263: public static boolean isWhitespace(char[] chars, int offset, int len) {
264: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
265: + "offset = " + offset //NOI18N
266: + ", len = " + len //NOI18N
267: + ", chars.length = " + chars.length; //NOI18N
268:
269: while (len > 0) {
270: if (!Character.isWhitespace(chars[offset])) {
271: return false;
272: }
273: offset++;
274: len--;
275: }
276: return true;
277: }
278:
279: /** Return the first index that is not space */
280: public static int findFirstNonTab(char[] chars, int offset, int len) {
281: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
282: + "offset = " + offset //NOI18N
283: + ", len = " + len //NOI18N
284: + ", chars.length = " + chars.length; //NOI18N
285:
286: while (len > 0) {
287: if (chars[offset] != '\t') { //NOI18N
288: return offset;
289: }
290: offset++;
291: len--;
292: }
293: return -1;
294: }
295:
296: /** Return the first index that is not space */
297: public static int findFirstNonSpace(char[] chars, int offset,
298: int len) {
299: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
300: + "offset = " + offset //NOI18N
301: + ", len = " + len //NOI18N
302: + ", chars.length = " + chars.length; //NOI18N
303:
304: while (len > 0) {
305: if (chars[offset] != ' ') { //NOI18N
306: return offset;
307: }
308: offset++;
309: len--;
310: }
311: return -1;
312: }
313:
314: /** Return the first index that is not space or tab or new-line char */
315: public static int findFirstNonWhite(char[] chars, int offset,
316: int len) {
317: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
318: + "offset = " + offset //NOI18N
319: + ", len = " + len //NOI18N
320: + ", chars.length = " + chars.length; //NOI18N
321:
322: while (len > 0) {
323: if (!Character.isWhitespace(chars[offset])) {
324: return offset;
325: }
326: offset++;
327: len--;
328: }
329: return -1;
330: }
331:
332: /** Return the last index that is not space or tab or new-line char */
333: public static int findLastNonWhite(char[] chars, int offset, int len) {
334: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
335: + "offset = " + offset //NOI18N
336: + ", len = " + len //NOI18N
337: + ", chars.length = " + chars.length; //NOI18N
338:
339: int i = offset + len - 1;
340: while (i >= offset) {
341: if (!Character.isWhitespace(chars[i])) {
342: return i;
343: }
344: i--;
345: }
346: return -1;
347: }
348:
349: /** Count the number of line feeds in char array.
350: * @return number of LF characters contained in array.
351: */
352: public static int getLFCount(char chars[]) {
353: return getLFCount(chars, 0, chars.length);
354: }
355:
356: public static int getLFCount(char chars[], int offset, int len) {
357: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
358: + "offset = " + offset //NOI18N
359: + ", len = " + len //NOI18N
360: + ", chars.length = " + chars.length; //NOI18N
361:
362: int lfCount = 0;
363: while (len > 0) {
364: if (chars[offset++] == '\n') { //NOI18N
365: lfCount++;
366: }
367: len--;
368: }
369: return lfCount;
370: }
371:
372: public static int getLFCount(String s) {
373: int lfCount = 0;
374: int len = s.length();
375: for (int i = 0; i < len; i++) {
376: if (s.charAt(i) == '\n') { //NOI18N
377: lfCount++;
378: }
379: }
380: return lfCount;
381: }
382:
383: public static int findFirstLFOffset(char[] chars, int offset,
384: int len) {
385: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
386: + "offset = " + offset //NOI18N
387: + ", len = " + len //NOI18N
388: + ", chars.length = " + chars.length; //NOI18N
389:
390: while (len > 0) {
391: if (chars[offset++] == '\n') { //NOI18N
392: return offset - 1;
393: }
394: len--;
395: }
396: return -1;
397: }
398:
399: public static int findFirstLFOffset(String s) {
400: int len = s.length();
401: for (int i = 0; i < len; i++) {
402: if (s.charAt(i) == '\n') { //NOI18N
403: return i;
404: }
405: }
406: return -1;
407: }
408:
409: public static int findFirstTab(char[] chars, int offset, int len) {
410: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
411: + "offset = " + offset //NOI18N
412: + ", len = " + len //NOI18N
413: + ", chars.length = " + chars.length; //NOI18N
414:
415: while (len > 0) {
416: if (chars[offset++] == '\t') { //NOI18N
417: return offset - 1;
418: }
419: len--;
420: }
421: return -1;
422: }
423:
424: public static int findFirstTabOrLF(char[] chars, int offset, int len) {
425: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
426: + "offset = " + offset //NOI18N
427: + ", len = " + len //NOI18N
428: + ", chars.length = " + chars.length; //NOI18N
429:
430: while (len > 0) {
431: switch (chars[offset++]) {
432: case '\t': //NOI18N
433: case '\n': //NOI18N
434: return offset - 1;
435: }
436: len--;
437: }
438: return -1;
439: }
440:
441: /** Reverses the order of characters in the array. It works from
442: * the begining of the array, so no offset is given.
443: */
444: public static void reverse(char[] chars, int len) {
445: for (int i = ((--len - 1) >> 1); i >= 0; --i) {
446: char ch = chars[i];
447: chars[i] = chars[len - i];
448: chars[len - i] = ch;
449: }
450: }
451:
452: public static boolean equals(String s, char[] chars) {
453: return equals(s, chars, 0, chars.length);
454: }
455:
456: public static boolean equals(String s, char[] chars, int offset,
457: int len) {
458: assert offset + len <= chars.length : "Invalid parameters: " //NOI18N
459: + "offset = " + offset //NOI18N
460: + ", len = " + len //NOI18N
461: + ", chars.length = " + chars.length; //NOI18N
462:
463: if (s.length() != len) {
464: return false;
465: }
466: for (int i = 0; i < len; i++) {
467: if (s.charAt(i) != chars[offset + i]) {
468: return false;
469: }
470: }
471: return true;
472: }
473:
474: /** Do initial reading of document. Translate any line separators
475: * found in document to line separators used by document. It also cares
476: * for elements that were already created on the empty document. Although
477: * the document must be empty there can be already marks created. Initial
478: * read is equivalent to inserting the string array of the whole document
479: * size at position 0 in the document. Therefore all the marks that are
480: * not insertAfter are removed and reinserted to the end of the document
481: * after the whole initial read is finished.
482: * @param doc document for which the initialization is performed
483: * @param reader reader from which document should be read
484: * @param lsType line separator type
485: * @param testLS test line separator of file and if it's consistent, use it
486: * @param markDistance the distance between the new syntax mark is put
487: */
488: public static void initialRead(BaseDocument doc, Reader reader,
489: boolean testLS) throws IOException {
490: // document MUST be empty
491: if (doc.getLength() > 0) {
492: return;
493: }
494:
495: // for valid reader read the document
496: if (reader != null) {
497: // Size of the read buffer
498: int readBufferSize = ((Integer) doc
499: .getProperty(SettingsNames.READ_BUFFER_SIZE))
500: .intValue();
501:
502: if (testLS) {
503: // Construct a reader that searches for initial line separator type
504: reader = new LineSeparatorConversion.InitialSeparatorReader(
505: reader);
506: }
507:
508: /* buffer into which the data from file will be read */
509: LineSeparatorConversion.ToLineFeed toLF = new LineSeparatorConversion.ToLineFeed(
510: reader, readBufferSize);
511:
512: boolean firstRead = true; // first cycle of reading from stream
513: int pos = 0; // actual position in the document data
514: int line = 0; // Line counter
515: int maxLineLength = 0; // Longest line found
516: int lineStartPos = 0; // Start offset of the last line
517: int markCount = 0; // Total mark count - for debugging only
518:
519: /* // array for getting mark array from renderer inner class
520: Mark[] origMarks = new Mark[doc.marks.getItemCount()];
521: ObjectArrayUtilities.copyItems(doc.marks, 0, origMarks.length,
522: origMarks, 0);
523:
524: // now remove all the marks that are not insert after
525: for (int i = 0; i < origMarks.length; i++) {
526: Mark mark = origMarks[i];
527: if (!(mark.getInsertAfter()
528: || (mark instanceof MarkFactory.CaretMark))
529: ) {
530: try {
531: mark.remove();
532: } catch (InvalidMarkException e) {
533: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
534: e.printStackTrace();
535: }
536: }
537: }
538: }
539: */
540:
541: // Enter the loop where all data from reader will be read
542: Segment text = toLF.nextConverted();
543: while (text != null) {
544: try {
545: doc.insertString(pos, new String(text.array,
546: text.offset, text.count), null);
547: } catch (BadLocationException e) {
548: throw new IllegalStateException(e.toString());
549: }
550: pos += text.count;
551: text = toLF.nextConverted();
552: }
553:
554: if (testLS) {
555: doc
556: .putProperty(
557: BaseDocument.READ_LINE_SEPARATOR_PROP,
558: ((LineSeparatorConversion.InitialSeparatorReader) reader)
559: .getInitialSeparator());
560: // if (doc.getProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP) == null) {
561: // doc.putProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP, newLS);
562: // }
563: // The property above is left empty so the write() will default to the READ_LINE_SEPARATOR_PROP
564: }
565:
566: /* // Now reinsert marks that were removed at begining to the end
567: for (int i = 0; i < origMarks.length; i++) {
568: Mark mark = origMarks[i];
569: if (!(mark.getInsertAfter()
570: || (mark instanceof MarkFactory.CaretMark))
571: ) {
572: try {
573: origMarks[i].insert(doc, pos);
574: } catch (InvalidMarkException e) {
575: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
576: e.printStackTrace();
577: }
578: } catch (BadLocationException e) {
579: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
580: e.printStackTrace();
581: }
582: }
583: }
584: }
585: */
586:
587: // Set the line limit document property
588: // [PENDING] doc.putProperty(BaseDocument.LINE_LIMIT_PROP, new Integer(maxLineLength));
589: }
590: }
591:
592: /** Read from some reader and insert into document */
593: static void read(BaseDocument doc, Reader reader, int pos)
594: throws BadLocationException, IOException {
595: int readBufferSize = ((Integer) doc
596: .getProperty(SettingsNames.READ_BUFFER_SIZE))
597: .intValue();
598: LineSeparatorConversion.ToLineFeed toLF = new LineSeparatorConversion.ToLineFeed(
599: reader, readBufferSize);
600:
601: Segment text = toLF.nextConverted();
602: while (text != null) {
603: doc.insertString(pos, new String(text.array, text.offset,
604: text.count), null);
605: pos += text.count;
606: text = toLF.nextConverted();
607: }
608: }
609:
610: /** Write from document to some writer */
611: static void write(BaseDocument doc, Writer writer, int pos, int len)
612: throws BadLocationException, IOException {
613: String lsType = (String) doc
614: .getProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP);
615: if (lsType == null) {
616: lsType = (String) doc
617: .getProperty(BaseDocument.READ_LINE_SEPARATOR_PROP);
618: if (lsType == null) {
619: lsType = BaseDocument.LS_LF;
620: }
621: }
622: int writeBufferSize = ((Integer) doc
623: .getProperty(SettingsNames.WRITE_BUFFER_SIZE))
624: .intValue();
625: char[] getBuf = new char[writeBufferSize];
626: char[] writeBuf = new char[2 * writeBufferSize];
627: int actLen = 0;
628:
629: while (len > 0) {
630: actLen = Math.min(len, writeBufferSize);
631: doc.getChars(pos, getBuf, 0, actLen);
632: int tgtLen = convertLFToLS(getBuf, actLen, writeBuf, lsType);
633: writer.write(writeBuf, 0, tgtLen);
634: pos += actLen;
635: len -= actLen;
636: }
637:
638: // Append new-line if not the last char
639: /* if (actLen > 0 && getBuf[actLen - 1] != '\n') {
640: writer.write(new char[] { '\n' }, 0, 1);
641: }
642: */
643:
644: }
645:
646: /** Get visual column. */
647: public static int getColumn(char buffer[], int offset, int len,
648: int tabSize, int startCol) {
649: int col = startCol;
650: int endOffset = offset + len;
651:
652: // Check wrong tab values
653: if (tabSize <= 0) {
654: new Exception("Wrong tab size=" + tabSize)
655: .printStackTrace(); // NOI18N
656: tabSize = 8;
657: }
658:
659: while (offset < endOffset) {
660: switch (buffer[offset++]) {
661: case '\t':
662: col = (col + tabSize) / tabSize * tabSize;
663: break;
664: default:
665: col++;
666: }
667: }
668: return col;
669: }
670:
671: /** Get buffer filled with appropriate number of spaces. The buffer
672: * can have actually more spaces than requested.
673: * @param numSpaces number of spaces
674: */
675: public static synchronized char[] getSpacesBuffer(int numSpaces) {
676: // check if there's enough space in white space array
677: while (numSpaces > spacesBuffer.length) {
678: char tmpBuf[] = new char[spacesBuffer.length * 2]; // new buffer
679: System.arraycopy(spacesBuffer, 0, tmpBuf, 0,
680: spacesBuffer.length);
681: System.arraycopy(spacesBuffer, 0, tmpBuf,
682: spacesBuffer.length, spacesBuffer.length);
683: spacesBuffer = tmpBuf;
684: }
685:
686: return spacesBuffer;
687: }
688:
689: /** Get string filled with space characters. There is optimization to return
690: * the same string instance for up to ceratin number of spaces.
691: * @param numSpaces number of spaces determining the resulting size of the string.
692: */
693: public static synchronized String getSpacesString(int numSpaces) {
694: if (numSpaces <= MAX_CACHED_SPACES_STRING_LENGTH) { // Cached
695: String ret = spacesStrings[numSpaces];
696: if (ret == null) {
697: ret = spacesStrings[MAX_CACHED_SPACES_STRING_LENGTH]
698: .substring(0, numSpaces);
699: spacesStrings[numSpaces] = ret;
700: }
701:
702: return ret;
703:
704: } else { // non-cached
705: return new String(getSpacesBuffer(numSpaces), 0, numSpaces);
706: }
707: }
708:
709: /** Get buffer of the requested size filled entirely with space character.
710: * @param numSpaces number of spaces in the returned character buffer.
711: */
712: public static char[] createSpacesBuffer(int numSpaces) {
713: char[] ret = new char[numSpaces];
714: System.arraycopy(getSpacesBuffer(numSpaces), 0, ret, 0,
715: numSpaces);
716: return ret;
717: }
718:
719: /** Get buffer filled with appropriate number of tabs. The buffer
720: * can have actually more tabs than requested.
721: * @param numSpaces number of spaces
722: */
723: public static char[] getTabsBuffer(int numTabs) {
724: // check if there's enough space in white space array
725: if (numTabs > tabsBuffer.length) {
726: char tmpBuf[] = new char[numTabs * 2]; // new buffer
727:
728: // initialize new buffer with spaces
729: for (int i = 0; i < tmpBuf.length; i += tabsBuffer.length) {
730: System.arraycopy(tabsBuffer, 0, tmpBuf, i, Math.min(
731: tabsBuffer.length, tmpBuf.length - i));
732: }
733: tabsBuffer = tmpBuf;
734: }
735:
736: return tabsBuffer;
737: }
738:
739: /** Get the string that should be used for indentation of the given level.
740: * @param indent indentation level
741: * @param expandTabs whether tabs should be expanded to spaces or not
742: * @param tabSize size substituted visually for the '\t' character
743: */
744: public static String getIndentString(int indent,
745: boolean expandTabs, int tabSize) {
746: return getWhitespaceString(0, indent, expandTabs, tabSize);
747: }
748:
749: /** Get the string that should be used for indentation of the given level.
750: * @param indent indentation level
751: * @param expandTabs whether tabs should be expanded to spaces or not
752: * @param tabSize size of the '\t' character
753: */
754: public static String getWhitespaceString(int startCol, int endCol,
755: boolean expandTabs, int tabSize) {
756: return (expandTabs || tabSize <= 0) ? getSpacesString(endCol
757: - startCol) : new String(createWhiteSpaceFillBuffer(
758: startCol, endCol, tabSize));
759: }
760:
761: /** createWhitespaceFillBuffer() with the non-capital 's' should be used.
762: * @deprecated
763: */
764: public static char[] createWhiteSpaceFillBuffer(int startCol,
765: int endCol, int tabSize) {
766: return createWhitespaceFillBuffer(startCol, endCol, tabSize);
767: }
768:
769: /** Get buffer filled with spaces/tabs so that it reaches from
770: * some column to some other column.
771: * @param startCol starting visual column of the whitespace on the line
772: * @param endCol ending visual column of the whitespace on the line
773: * @param tabSize size substituted visually for the '\t' character
774: */
775: public static char[] createWhitespaceFillBuffer(int startCol,
776: int endCol, int tabSize) {
777: if (startCol >= endCol) {
778: return EMPTY_CHAR_ARRAY;
779: }
780:
781: // Check wrong tab values
782: if (tabSize <= 0) {
783: new Exception("Wrong tab size=" + tabSize)
784: .printStackTrace(); // NOI18N
785: tabSize = 8;
786: }
787:
788: int tabs = 0;
789: int spaces = 0;
790: int nextTab = (startCol + tabSize) / tabSize * tabSize;
791: if (nextTab > endCol) { // only spaces
792: spaces += endCol - startCol;
793: } else { // at least one tab
794: tabs++; // jump to first tab
795: int endSpaces = endCol - endCol / tabSize * tabSize;
796: tabs += (endCol - endSpaces - nextTab) / tabSize;
797: spaces += endSpaces;
798: }
799:
800: char[] ret = new char[tabs + spaces];
801: if (tabs > 0) {
802: System.arraycopy(getTabsBuffer(tabs), 0, ret, 0, tabs);
803: }
804: if (spaces > 0) {
805: System.arraycopy(getSpacesBuffer(spaces), 0, ret, tabs,
806: spaces);
807: }
808: return ret;
809: }
810:
811: /** Loads the file and performs conversion of line separators to LF.
812: * This method can be used in debuging of syntax scanner or somewhere else.
813: * @param fileName the name of the file to load
814: * @return array of loaded characters with '\n' as line separator
815: */
816: public static char[] loadFile(String fileName) throws IOException {
817: File file = new File(fileName);
818: char chars[] = new char[(int) file.length()];
819: FileReader reader = new FileReader(file);
820: reader.read(chars);
821: reader.close();
822: int len = Analyzer.convertLSToLF(chars, chars.length);
823: if (len != chars.length) {
824: char copyChars[] = new char[len];
825: System.arraycopy(chars, 0, copyChars, 0, len);
826: chars = copyChars;
827: }
828: return chars;
829: }
830:
831: /** Convert text with LF line separators to text that uses
832: * line separators of the document. This function is used when
833: * saving text into the file. Segment's data are converted inside
834: * the segment's data or new segment's data array is allocated.
835: * NOTE: Source segment must have just LFs as separators! Otherwise
836: * the conversion won't work correctly.
837: * @param src source chars to convert from
838: * @param len length of valid part of src data
839: * @param tgt target chars to convert to. The array MUST have twice
840: * the size of src otherwise index exception can be thrown
841: * @param lsType line separator type to be used i.e. LS_LF, LS_CR, LS_CRLF
842: * @return length of valid chars in tgt array
843: */
844: public static int convertLFToLS(char[] src, int len, char[] tgt,
845: String lsType) {
846: if (lsType != null && lsType.length() == 1) {
847: char ls = lsType.charAt(0);
848: if (ls == '\r' || ls == LineSeparatorConversion.LS
849: || ls == LineSeparatorConversion.PS) {
850: // now do conversion for LS_CR and Unicode LS, PS
851: for (int i = 0; i < len; i++) {
852: if (src[i] == '\n') {
853: tgt[i] = ls;
854: } else {
855: tgt[i] = src[i];
856: }
857: }
858: return len;
859: }
860: } else if (lsType.equals(BaseDocument.LS_CRLF)) {
861: int tgtLen = 0;
862: int moveStart = 0; // start of block that must be moved
863: int moveLen; // length of chars moved
864:
865: for (int i = 0; i < len; i++) {
866: if (src[i] == '\n') { // '\n' found
867: moveLen = i - moveStart;
868: if (moveLen > 0) { // will need to arraycopy
869: System.arraycopy(src, moveStart, tgt, tgtLen,
870: moveLen);
871: tgtLen += moveLen;
872: }
873: tgt[tgtLen++] = '\r';
874: tgt[tgtLen++] = '\n';
875: moveStart = i + 1; // skip separator
876: }
877: }
878:
879: // now move the rest if it's necessary
880: moveLen = len - moveStart;
881: if (moveLen > 0) {
882: System.arraycopy(src, moveStart, tgt, tgtLen, moveLen);
883: tgtLen += moveLen;
884: }
885: return tgtLen;
886: }
887: // Using either \n or line separator is unknown
888: System.arraycopy(src, 0, tgt, 0, len);
889: return len;
890: }
891:
892: public static boolean startsWith(char[] chars, char[] prefix) {
893: if (chars == null || chars.length < prefix.length) {
894: return false;
895: }
896: for (int i = 0; i < prefix.length; i++) {
897: if (chars[i] != prefix[i]) {
898: return false;
899: }
900: }
901: return true;
902: }
903:
904: public static boolean endsWith(char[] chars, char[] suffix) {
905: if (chars == null || chars.length < suffix.length) {
906: return false;
907: }
908: for (int i = chars.length - suffix.length; i < chars.length; i++) {
909: if (chars[i] != suffix[i]) {
910: return false;
911: }
912: }
913: return true;
914: }
915:
916: public static char[] concat(char[] chars1, char[] chars2) {
917: if (chars1 == null || chars1.length == 0) {
918: return chars2;
919: }
920: if (chars2 == null || chars2.length == 0) {
921: return chars1;
922: }
923: char[] ret = new char[chars1.length + chars2.length];
924: System.arraycopy(chars1, 0, ret, 0, chars1.length);
925: System.arraycopy(chars2, 0, ret, chars1.length, chars2.length);
926: return ret;
927: }
928:
929: public static char[] extract(char[] chars, int offset, int len) {
930: char[] ret = new char[len];
931: System.arraycopy(chars, offset, ret, 0, len);
932: return ret;
933: }
934:
935: public static boolean blocksHit(int[] blocks, int startPos,
936: int endPos) {
937: return (blocksIndex(blocks, startPos, endPos) >= 0);
938: }
939:
940: public static int blocksIndex(int[] blocks, int startPos, int endPos) {
941: if (blocks.length > 0) {
942: int onlyEven = ~1;
943: int low = 0;
944: int high = blocks.length - 2;
945:
946: while (low <= high) {
947: int mid = ((low + high) / 2) & onlyEven;
948:
949: if (blocks[mid + 1] <= startPos) {
950: low = mid + 2;
951: } else if (blocks[mid] >= endPos) {
952: high = mid - 2;
953: } else {
954: return low; // found
955: }
956: }
957: }
958:
959: return -1;
960: }
961:
962: /** Remove all spaces from the given string.
963: * @param s original string
964: * @return string with all spaces removed
965: */
966: public static String removeSpaces(String s) {
967: int spcInd = s.indexOf(' ');
968: if (spcInd >= 0) {
969: StringBuffer sb = new StringBuffer(s.substring(0, spcInd));
970: int sLen = s.length();
971: for (int i = spcInd + 1; i < sLen; i++) {
972: char ch = s.charAt(i);
973: if (ch != ' ') {
974: sb.append(ch);
975: }
976: }
977: return sb.toString();
978: }
979: return s;
980: }
981:
982: }
|