KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > output2 > AbstractLines


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.core.output2;
20
21 import java.io.File JavaDoc;
22 import java.io.FileOutputStream JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.nio.ByteBuffer JavaDoc;
25 import java.nio.CharBuffer JavaDoc;
26 import java.nio.channels.FileChannel JavaDoc;
27 import java.nio.charset.CharacterCodingException JavaDoc;
28 import java.nio.charset.Charset JavaDoc;
29 import java.nio.charset.CharsetEncoder JavaDoc;
30 import java.util.logging.Logger JavaDoc;
31 import java.util.regex.Matcher JavaDoc;
32 import java.util.regex.Pattern JavaDoc;
33 import javax.swing.event.ChangeEvent JavaDoc;
34 import javax.swing.event.ChangeListener JavaDoc;
35 import org.openide.util.Exceptions;
36 import org.openide.util.Mutex;
37 import org.openide.windows.OutputListener;
38
39 /**
40  * Abstract Lines implementation with handling for getLine wrap calculations, etc.
41  */

42 abstract class AbstractLines implements Lines, Runnable JavaDoc {
43     /** A collections-like lineStartList that maps file positions to getLine numbers */
44     IntList lineStartList;
45     /** Maps output listeners to the lines they are associated with */
46     IntMap linesToListeners;
47     private int longestLine = 0;
48     private static Boolean JavaDoc unitTestUseCache = null;
49     private int knownCharCount = -1;
50     private SparseIntList knownLogicalLineCounts = null;
51     private int lastCharCountForWrapCalculation = -1;
52     private int lastWrappedLineCount = -1;
53     private int lastCharCountForWrapAboveCalculation = -1;
54     private int lastWrappedAboveLineCount = -1;
55     private int lastWrappedAboveLine = -1;
56     private IntList errLines = null;
57
58
59     AbstractLines() {
60         if (Controller.LOG) Controller.log ("Creating a new AbstractLines");
61         init();
62     }
63
64     protected abstract Storage getStorage();
65
66     protected abstract boolean isDisposed();
67
68     protected abstract boolean isTrouble();
69
70     protected abstract void handleException (Exception JavaDoc e);
71
72     public char[] getText (int start, int end, char[] chars) {
73         if (isDisposed() || isTrouble()) {
74              //There is a breif window of opportunity for a window to display
75
//a disposed document. Should never appear on screen, but it can
76
//be requested to calculate the preferred size this will
77
//make sure it's noticable if it does.
78
if (Controller.LOG) {
79                  Controller.log (this + " !!!!!REQUEST FOR SUBRANGE " + start + "-" + end + " AFTER OUTWRITER HAS BEEN DISPOSED!!");
80
81              }
82              char[] msg = "THIS OUTPUT HAS BEEN DISPOSED! ".toCharArray();
83              if (chars == null) {
84                  chars = new char[end - start];
85              }
86              int pos = 0;
87              for (int i=0; i < chars.length; i++) {
88                  if (pos == msg.length - 1) {
89                      pos = 0;
90                  }
91                  chars[i] = msg[pos];
92                  pos++;
93              }
94              return chars;
95         }
96         if (end < start) {
97             throw new IllegalArgumentException JavaDoc ("Illogical text range from " +
98                 start + " to " + end);
99         }
100         synchronized(readLock()) {
101             int fileStart = AbstractLines.toByteIndex(start);
102             int byteCount = AbstractLines.toByteIndex(end - start);
103             try {
104                 CharBuffer JavaDoc chb = getStorage().getReadBuffer(fileStart, byteCount).asCharBuffer();
105                 //#68386 satisfy the request as much as possible, but if there's not enough remaining
106
// content, not much we can do..
107
int len = Math.min(end - start, chb.remaining());
108                 if (chars.length < len) {
109                     chars = new char[len];
110                 }
111                 chb.get(chars, 0, len);
112                 return chars;
113             } catch (Exception JavaDoc e) {
114                 handleException (e);
115                 return new char[0];
116             }
117         }
118     }
119
120     public String JavaDoc getText (int start, int end) {
121         if (isDisposed() || isTrouble()) {
122             return new String JavaDoc (new char[end - start]);
123         }
124         if (end < start) {
125             throw new IllegalArgumentException JavaDoc ("Illogical text range from " +
126                 start + " to " + end);
127         }
128         synchronized(readLock()) {
129             int fileStart = AbstractLines.toByteIndex(start);
130             int byteCount = AbstractLines.toByteIndex(end - start);
131             int available = getStorage().size();
132             if (available < fileStart + byteCount) {
133                 throw new ArrayIndexOutOfBoundsException JavaDoc ("Bytes from " +
134                     fileStart + " to " + (fileStart + byteCount) + " requested, " +
135                     "but storage is only " + available + " bytes long");
136             }
137             try {
138                 return getStorage().getReadBuffer(fileStart, byteCount).asCharBuffer().toString();
139             } catch (Exception JavaDoc e) {
140                 handleException (e);
141                 return new String JavaDoc(new char[end - start]);
142             }
143         }
144     }
145
146     private int lastErrLineMarked = -1;
147     void markErr() {
148         if (isTrouble() || getStorage().isClosed()) {
149             return;
150         }
151         if (errLines == null) {
152             errLines = new IntList(20);
153         }
154         int linecount = getLineCount();
155         //Check this - for calls to OutputWriter.write(byte b), we may still be on the same line as last time
156
if (linecount != lastErrLineMarked) {
157             errLines.add(linecount == 0 ? 0 : linecount-1);
158             lastErrLineMarked = linecount;
159         }
160     }
161
162     public boolean isErr(int line) {
163         return errLines != null ? errLines.contains(line) : false;
164     }
165
166     private ChangeListener JavaDoc listener = null;
167     public void addChangeListener(ChangeListener JavaDoc cl) {
168         this.listener = cl;
169         synchronized(this) {
170             if (getLineCount() > 0) {
171                 //May be a new tab for an old output, hide and reshow, etc.
172
fire();
173             }
174         }
175     }
176
177     public void removeChangeListener (ChangeListener JavaDoc cl) {
178         if (listener == cl) {
179             listener = null;
180         }
181     }
182
183     public void fire() {
184         if (isTrouble()) {
185             return;
186         }
187         if (Controller.LOG) Controller.log (this + ": Writer firing " + getStorage().size() + " bytes written");
188         if (listener != null) {
189             Mutex.EVENT.readAccess(this);
190         }
191     }
192
193     public void run() {
194         if (listener != null) {
195             listener.stateChanged(new ChangeEvent JavaDoc(this));
196         }
197     }
198
199     public boolean hasHyperlinks() {
200         return firstListenerLine() != -1;
201     }
202
203     public boolean isHyperlink (int line) {
204         return getListenerForLine(line) != null;
205     }
206
207     private void init() {
208         knownLogicalLineCounts = null;
209         lineStartList = new IntList(100);
210         linesToListeners = new IntMap();
211         setLastWrappedLineCount(-1);
212         longestLine = 0;
213         errLines = null;
214         matcher = null;
215         listener = null;
216         dirty = false;
217     }
218
219     private boolean dirty;
220
221     public boolean checkDirty(boolean clear) {
222         if (isTrouble()) {
223             return false;
224         }
225         boolean wasDirty = dirty;
226         if (clear) {
227             dirty = false;
228         }
229         return wasDirty;
230     }
231
232     public int[] allListenerLines() {
233         return linesToListeners.getKeys();
234     }
235
236     void clear() {
237         init();
238     }
239
240     public int getCharCount() {
241         if (isDisposed() || isTrouble()) return 0;
242         Storage storage = getStorage();
243         return storage == null ? 0 : AbstractLines.toCharIndex(getStorage().size());
244     }
245
246     /**
247      * Get a single getLine as a string.
248      */

249     public String JavaDoc getLine (int idx) throws IOException JavaDoc {
250         if (isDisposed() || isTrouble()) {
251             return ""; //NOI18N
252
}
253         int lineStart = lineStartList.get(idx);
254         int lineEnd;
255         if (idx != lineStartList.size()-1) {
256             lineEnd = lineStartList.get(idx+1);
257         } else {
258             lineEnd = getStorage().size();
259         }
260         CharBuffer JavaDoc cb = getStorage().getReadBuffer(lineStart,
261             lineEnd - lineStart).asCharBuffer();
262
263         char chars[] = new char[cb.limit()];
264         cb.get (chars);
265         return new String JavaDoc (chars);
266     }
267
268     /**
269      * Get a length of single line in bytes.
270      */

271     private int getLineLength(int idx) {
272         int lineStart = lineStartList.get(idx);
273         int lineEnd;
274         if (idx != lineStartList.size()-1) {
275             lineEnd = lineStartList.get(idx+1);
276         } else {
277             lineEnd = getStorage().size();
278         }
279         return lineEnd - lineStart;
280     }
281
282     public boolean isLineStart (int chpos) {
283         int bpos = toByteIndex(chpos);
284         return lineStartList.contains (bpos);
285     }
286
287     /**
288      * Returns the length of the specified lin characters.
289      *
290      * @param idx A getLine number
291      * @return The number of characters
292      */

293     public int length (int idx) {
294         if (isDisposed() || isTrouble()) {
295             return 0;
296         }
297         if (lineStartList.size() == 0) {
298             return 0;
299         }
300         int lineStart = lineStartList.get(idx);
301         int lineEnd;
302         if (idx != lineStartList.size()-1) {
303             lineEnd = lineStartList.get(idx+1);
304         } else {
305             lineEnd = getStorage().size();
306         }
307         return toCharIndex(lineEnd - lineStart);
308     }
309
310     /**
311      * Get the <strong>character</strong> index of a getLine as a position in
312      * the output file.
313      */

314     public int getLineStart (int line) {
315         if (isDisposed() || isTrouble()) return 0;
316         if (lineStartList.size() == 0) return 0;
317         return toCharIndex(lineStartList.get(line));
318     }
319
320     /** Get the getLine number of a <strong>character</strong> index in the
321      * file (as distinct from a byte position)
322      */

323     public int getLineAt (int position) {
324         if (isDisposed() || isTrouble()) return -1;
325         int bytePos = toByteIndex (position);
326         int i = lineStartList.indexOf (bytePos);
327         if (i != -1) {
328             return i;
329         }
330         return lineStartList.findNearest(bytePos);
331     }
332
333     public int getLineCount() {
334         if (isDisposed() || isTrouble()) return 0;
335         return lineStartList.size();
336     }
337
338     public OutputListener getListenerForLine (int line) {
339         return (OutputListener) linesToListeners.get(line);
340     }
341
342     public int firstListenerLine () {
343         if (isDisposed() || isTrouble()) return -1;
344         return linesToListeners.isEmpty() ? -1 : linesToListeners.first();
345     }
346
347     public int nearestListenerLine (int line, boolean backward) {
348         if (isDisposed() || isTrouble()) return -1;
349         return linesToListeners.nearest (line, backward);
350     }
351
352     public int getLongestLineLength() {
353         return toCharIndex(longestLine);
354     }
355
356
357      public void toLogicalLineIndex (final int[] physIdx, int charsPerLine) {
358          int physicalLine = physIdx[0];
359          physIdx[1] = 0;
360
361          if (physicalLine == 0) {
362              //First getLine never has lines above it
363
physIdx[1] = 0;
364              physIdx[2] = (length(physicalLine) / charsPerLine);
365          }
366
367          if (charsPerLine >= getLongestLineLength() || (getLineCount() <= 1)) {
368              //The doc is empty, or there are no lines long enough to wrap anyway
369
physIdx[1] = 0;
370              physIdx[2] = 1;
371              return;
372          }
373
374          int logicalLine =
375              findFirstLineWithoutMoreLinesAboveItThan (physicalLine, charsPerLine);
376
377          int linesAbove = getLogicalLineCountAbove(logicalLine, charsPerLine);
378
379          int len = length(logicalLine);
380
381          int wrapCount = len > charsPerLine ? (len / charsPerLine) + 1 : 1;
382
383          physIdx[0] = logicalLine;
384          int lcount = linesAbove + wrapCount;
385          physIdx[1] = (wrapCount - (lcount - physicalLine));
386
387          physIdx[2] = wrapCount;
388      }
389
390      /**
391       * Uses a divide-and-conquer approach to quickly locate a getLine which has
392       * the specified number of logical lines above it. For large output, this
393       * data is cached in OutWriter in a sparse int array. This method is called
394       * from viewToModel, so it must be very, very fast - it may be called once
395       * every time the mouse is moved, to determine if the cursor should be
396       * updated.
397       */

398      private int findFirstLineWithoutMoreLinesAboveItThan (int target, int charsPerLine) {
399          int start = 0;
400          int end = getLineCount();
401          int midpoint = start + ((end - start) >> 1);
402          int linesAbove = getLogicalLineCountAbove(midpoint, charsPerLine);
403          int result = divideAndConquer (target, start, midpoint, end, charsPerLine, linesAbove);
404
405          return Math.min(end, result) -1;
406      }
407      /**
408       * Recursively search for the line number with the smallest number of lines
409       * above it, greater than the passed target number of lines. This is
410       * effectively a binary search - divides the range of lines in half and
411       * checks if the middle value is greater than the target; then recurses on
412       * itself with whatever half of the range of lines has a better chance at
413       * containing a smaller value.
414       * <p>
415       * It is primed with an initial call with the start, midpoint and end values.
416       */

417      private int divideAndConquer (int target, int start, int midpoint, int end, int charsPerLine, int midValue) {
418          //We have an exact match - we're done
419
if (midValue == target) {
420              return midpoint + 1;
421          }
422
423          //In any of these conditions, the search has run out of gas - the
424
//end value must be the match
425
if (end - start <= 1 || midpoint == start || midpoint == end) {
426              return end;
427          }
428
429          if (midValue > target) {
430              //The middle value is greater than the target - look for a closer
431
//match between the first and the middle getLine
432

433              int upperMidPoint = start + ((midpoint - start) >> 1);
434              if ((midpoint - start) % 2 != 0) {
435                  upperMidPoint++;
436              }
437              int upperMidValue = getLogicalLineCountAbove(upperMidPoint, charsPerLine);
438              return divideAndConquer (target, start, upperMidPoint, midpoint, charsPerLine, upperMidValue);
439          } else {
440              //The middle value is less than the target - look for a match
441
//between the midpoint and the last getLine
442

443              int lowerMidPoint = ((end - start) >> 2) + midpoint;
444              if ((end - midpoint) % 2 != 0) {
445                  lowerMidPoint++;
446              }
447              int lowerMidValue = getLogicalLineCountAbove(lowerMidPoint, charsPerLine);
448              return divideAndConquer (target, midpoint, lowerMidPoint, end, charsPerLine, lowerMidValue);
449          }
450      }
451
452
453     /**
454      * Get the number of logical lines if character wrapped at the specified
455      * width. Calculates on the fly below 100000 characters, above that builds
456      * a cache on the first call and uses that.
457      */

458     public int getLogicalLineCountAbove (int line, int charCount) {
459         if (line == 0) {
460             return 0;
461         }
462         if (toByteIndex(charCount) > longestLine) {
463             return line;
464         }
465
466         //See OutputDocumentTest.testWordWrapping
467
if (unitTestUseCache != null) {
468             if (Boolean.TRUE.equals(unitTestUseCache)) {
469                 return dynLogicalLineCountAbove(line, charCount);
470             } else {
471                 return cachedLogicalLineCountAbove (line, charCount);
472             }
473         }
474
475         if (getStorage().size() < 100000) {
476             return dynLogicalLineCountAbove(line, charCount);
477         } else {
478             return cachedLogicalLineCountAbove (line, charCount);
479         }
480     }
481
482     /**
483      * Get the number of logical lines above a given physical getLine if character
484      * wrapped at the specified
485      * width. Calculates on the fly below 100000 characters, above that builds
486      * a cache on the first call and uses that.
487      */

488     public int getLogicalLineCountIfWrappedAt (int charCount) {
489         if (toByteIndex(charCount) > longestLine) {
490             return getLineCount();
491         }
492
493         if (unitTestUseCache != null) {
494             if (Boolean.TRUE.equals(unitTestUseCache)) {
495                 return dynLogicalLineCountIfWrappedAt(charCount);
496             } else {
497                 return cachedLogicalLineCountIfWrappedAt(charCount);
498             }
499         }
500
501         if (getStorage().size() < 100000) {
502             return dynLogicalLineCountIfWrappedAt(charCount);
503         } else {
504             return cachedLogicalLineCountIfWrappedAt(charCount);
505         }
506     }
507
508     public void addListener (int line, OutputListener l, boolean important) {
509         if (l == null) {
510             //#56826 - debug messaging
511
Logger.getAnonymousLogger().warning("Issue #56826 - Adding a null OutputListener for line:" + line);
512         } else {
513             linesToListeners.put(line, l);
514             if (important) {
515                 importantLines.add(line);
516             }
517         }
518     }
519     
520     private IntList importantLines = new IntList(10);
521     
522     public int firstImportantListenerLine() {
523         return importantLines.size() == 0 ? -1 : importantLines.get(0);
524     }
525     
526     public boolean isImportantHyperlink(int line) {
527         return importantLines.contains(line);
528     }
529     
530     /**
531      * A minor hook to let unit tests decide if caching is on or off.
532      * @param val
533      */

534     static void unitTestUseCache (Boolean JavaDoc val) {
535         unitTestUseCache = val;
536     }
537
538     private int cachedLogicalLineCountAbove (int line, int charCount) {
539         if (charCount != knownCharCount || knownLogicalLineCounts == null) {
540             knownCharCount = charCount;
541             calcCharCounts(charCount);
542         }
543         return knownLogicalLineCounts.get(line);
544     }
545
546     private int cachedLogicalLineCountIfWrappedAt (int charCount) {
547         int lineCount = getLineCount();
548         if (charCount == 0 || lineCount == 0) {
549             return 0;
550         }
551         synchronized (readLock()) {
552             if (charCount != knownCharCount || knownLogicalLineCounts == null) {
553                 knownCharCount = charCount;
554                 calcCharCounts(charCount);
555             }
556             int result = knownLogicalLineCounts.get(lineCount-1);
557             int len = length (lineCount - 1);
558             result += (len / charCount) + 1;
559             return result;
560         }
561     }
562
563     /**
564      * Builds a cache of lines which are longer than the last known width, which
565      * can be retained for future lookups. Finding the logical position of a
566      * getLine when wrapped means iterating all the lines above it. Above a
567      * threshold, it is much preferable to cache it. We use SparseIntList to
568      * create a cache which only actually holds the counts for lines that *are*
569      * wrapped, and interpolates the rest, so we don't need to create an int[]
570      * as big as the number of lines we have. This presumes that most lines
571      * don't wrap.
572      */

573     private void calcCharCounts(int width) {
574         synchronized (readLock()) {
575             int lineCount = getLineCount();
576             knownLogicalLineCounts = new SparseIntList(30);
577
578             int val = 0;
579             for (int i=1; i < lineCount; i++) {
580                 int len = length(i);
581
582                 if (len > width) {
583                     val += (len / width) + 1;
584                     knownLogicalLineCounts.add(val, i);
585                 } else {
586                     val++;
587                 }
588             }
589
590             knownCharCount = width;
591         }
592     }
593
594     /**
595      * Gets the number of lines the document will require if getLine wrapped at the
596      * specified character index.
597      */

598     private int dynLogicalLineCountIfWrappedAt (int charCount) {
599
600         synchronized (readLock()) {
601             int bcount = toByteIndex(charCount);
602             if (longestLine <= bcount) {
603                 return lineStartList.size();
604             }
605             if (charCount == lastCharCountForWrapCalculation && lastWrappedLineCount != -1) {
606                 return lastWrappedLineCount;
607             }
608             if (lineStartList.size() == 0) {
609                 return 0;
610             }
611             int nOfLines = lineStartList.size();
612             int lineCount = 0;
613             for (int i=0; i < nOfLines; i++) {
614                 int currLength = getLineLength(i);
615                 lineCount += (currLength > bcount) ? ((currLength / bcount) + 1) : 1;
616             }
617             setLastCharCountForWrapCalculation(charCount);
618             lastWrappedLineCount = lineCount;
619             return lineCount;
620         }
621     }
622
623     /**
624      * Gets the number of lines that occur *above* a given getLine if wrapped at the
625      * specified char count.
626      *
627      * @param line The getLine in question
628      * @param charCount The number of characters at which to wrap
629      * @return The number of logical wrapped lines above the passed getLine
630      */

631     private int dynLogicalLineCountAbove (int line, int charCount) {
632         int lineCount = getLineCount();
633         if (line == 0 || lineCount == 0) {
634             return 0;
635         }
636         if (charCount == lastCharCountForWrapAboveCalculation && lastWrappedAboveLineCount != -1 && line == lastWrappedAboveLine) {
637             return lastWrappedAboveLineCount;
638         }
639
640         synchronized (readLock()) {
641             lastWrappedAboveLineCount = 0;
642
643             for (int i=0; i < line; i++) {
644                 int len = length(i);
645                 if (len > charCount) {
646                     lastWrappedAboveLineCount += (len / charCount) + 1;
647                 } else {
648                     lastWrappedAboveLineCount++;
649                 }
650             }
651
652             lastWrappedAboveLine = line;
653             setLastCharCountForWrapAboveCalculation(charCount);
654             return lastWrappedAboveLineCount;
655         }
656     }
657     
658     void markDirty() {
659         dirty = true;
660     }
661
662     public void lineStarted(int start) {
663         if (Controller.VERBOSE) Controller.log("AbstractLines.lineStarted " + start); //NOI18N
664
int lineCount = 0;
665         synchronized (readLock()) {
666             setLastWrappedLineCount(-1);
667             setLastCharCountForWrapAboveCalculation(-1);
668             lineStartList.add(start);
669             matcher = null;
670             lineCount = lineStartList.size();
671         }
672         if (lineCount == 20 || lineCount == 10 || lineCount == 1) {
673             //Fire again after the first 20 lines
674
if (Controller.LOG) Controller.log("Firing initial write event");
675             fire();
676         }
677     }
678     
679     public void lineFinished(int lineLength) {
680         synchronized (readLock()) {
681             setLastWrappedLineCount(-1);
682             setLastCharCountForWrapAboveCalculation(-1);
683             longestLine = Math.max(longestLine, lineLength);
684             matcher = null;
685             
686             int lineCount = lineStartList.size();
687             //This is the index of the getLine we just added
688
int lastline = lineCount-1;
689             checkLogicalLineCount(lastline);
690         }
691     }
692     
693     private void checkLogicalLineCount(int lastline) {
694         //If we already have enough lines that we need to cache logical getLine
695
//lengths, update the cache - rebuilding it is very expensive
696
if (knownLogicalLineCounts != null) {
697             //Get the length of the getLine
698
int len = length(lastline);
699             
700             //We only need to add if it will wrap - SparseIntList's get()
701
//semantics takes care of non-wrapped lines
702
if (len > knownCharCount) {
703                 int aboveLineCount;
704                 if (knownLogicalLineCounts.lastIndex() != -1) {
705                     //If the cache already has some entries, calculate the
706
//values from the last entry - this is less expensive
707
//than looking it up
708
aboveLineCount = (lastline - (knownLogicalLineCounts.lastIndex() + 1)) + knownLogicalLineCounts.lastAdded();
709                 } else {
710                     //Otherwise, it's just the number of lines above this
711
//one - it's the first entry
712
aboveLineCount = Math.max(0, lastline-1);
713                 }
714                 //Add in the number of times this getLine will wrap
715
aboveLineCount += (len / knownCharCount) + 1;
716                 knownLogicalLineCounts.add(aboveLineCount, lastline);
717             }
718         }
719
720     }
721     
722     public void lineWritten(int start, int lineLength) {
723         if (Controller.VERBOSE) Controller.log("AbstractLines.lineWritten " + start + " length:" + lineLength); //NOI18N
724
int lineCount = 0;
725         synchronized (readLock()) {
726             setLastWrappedLineCount(-1);
727             setLastCharCountForWrapAboveCalculation(-1);
728             longestLine = Math.max(longestLine, lineLength);
729             lineStartList.add(start);
730             matcher = null;
731             
732             lineCount = lineStartList.size();
733             //This is the index of the getLine we just added
734
int lastline = lineCount-1;
735             checkLogicalLineCount(lastline);
736             
737         }
738         if (lineCount == 20 || lineCount == 10 || lineCount == 1) {
739             //Fire again after the first 20 lines
740
if (Controller.LOG) Controller.log("Firing initial write event");
741             fire();
742         }
743     }
744
745     /** Convert an index from chars to byte count (*2). Simple math, but it
746      * makes the intent clearer when encountered in code */

747     static int toByteIndex (int charIndex) {
748         return charIndex << 1;
749     }
750
751     /** Convert an index from bytes to chars (/2). Simple math, but it
752      * makes the intent clearer when encountered in code */

753     static int toCharIndex (int byteIndex) {
754         assert byteIndex % 2 == 0 : "bad index: " + byteIndex; //NOI18N
755
return byteIndex >> 1;
756     }
757
758     public void saveAs(String JavaDoc path) throws IOException JavaDoc {
759         if (getStorage()== null) {
760             throw new IOException JavaDoc ("Data has already been disposed"); //NOI18N
761
}
762         File JavaDoc f = new File JavaDoc (path);
763         CharBuffer JavaDoc cb = getStorage().getReadBuffer(0, getStorage().size()).asCharBuffer();
764
765         FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc (f);
766         try {
767             String JavaDoc encoding = System.getProperty ("file.encoding"); //NOI18N
768
if (encoding == null) {
769                 encoding = "UTF-8"; //NOI18N
770
}
771             Charset JavaDoc charset = Charset.forName (encoding); //NOI18N
772
CharsetEncoder JavaDoc encoder = charset.newEncoder ();
773             ByteBuffer JavaDoc bb = encoder.encode (cb);
774             FileChannel JavaDoc ch = fos.getChannel();
775             ch.write(bb);
776             ch.close();
777         } finally {
778             fos.close();
779         }
780     }
781
782     private String JavaDoc lastSearchString = null;
783     private Matcher JavaDoc matcher = null;
784     public Matcher JavaDoc getForwardMatcher() {
785         return matcher;
786     }
787
788     public Matcher JavaDoc getReverseMatcher() {
789         try {
790             Storage storage = getStorage();
791             if (storage == null) {
792                 return null;
793             }
794             if (matcher != null && lastSearchString != null && lastSearchString.length() > 0 && storage.size() > 0) {
795                 StringBuffer JavaDoc sb = new StringBuffer JavaDoc (lastSearchString);
796                 sb.reverse();
797                 CharBuffer JavaDoc buf = storage.getReadBuffer(0, storage.size()).asCharBuffer();
798                 //This could be very slow for large amounts of data
799
StringBuffer JavaDoc data = new StringBuffer JavaDoc (buf.toString());
800                 data.reverse();
801
802                 Pattern JavaDoc pat = escapePattern(sb.toString());
803                 return pat.matcher(data);
804             }
805         } catch (Exception JavaDoc e) {
806             Exceptions.printStackTrace(e);
807         }
808         return null;
809     }
810
811     public Matcher JavaDoc find(String JavaDoc s) {
812         if (Controller.LOG) Controller.log (this + ": Executing find for string " + s + " on " );
813         Storage storage = getStorage();
814         if (storage == null) {
815             return null;
816         }
817         if (matcher != null && s.equals(lastSearchString)) {
818             return matcher;
819         }
820         try {
821             int size = storage.size();
822             if (size > 0) {
823                 Pattern JavaDoc pat = escapePattern(s);
824                 CharBuffer JavaDoc buf = storage.getReadBuffer(0, size).asCharBuffer();
825                 Matcher JavaDoc m = pat.matcher(buf);
826                 if (!m.find(0)) {
827                     return null;
828                 }
829                 matcher = m;
830                 matcher.reset();
831                 lastSearchString = s;
832                 return matcher;
833             }
834         } catch (IOException JavaDoc ioe) {
835             Exceptions.printStackTrace(ioe);
836         }
837         return null;
838     }
839     
840     /**
841      * escape all the special regexp characters to simulate plain search using regexp..
842      *
843      */

844     static Pattern JavaDoc escapePattern(String JavaDoc s) {
845         // fix for issue #50170, test for this method created, if necessary refine..
846
// [jglick] Probably this would work as well and be a bit more readable:
847
// String replacement = "\\Q" + s + "\\E";
848
String JavaDoc replacement = s.replaceAll("([\\(\\)\\[\\]\\^\\*\\.\\$\\{\\}\\?\\+\\\\])", "\\\\$1");
849         return Pattern.compile(replacement, Pattern.CASE_INSENSITIVE);
850     }
851
852     private void setLastCharCountForWrapCalculation(int lastCharCountForWrapCalculation) {
853         this.lastCharCountForWrapCalculation = lastCharCountForWrapCalculation;
854     }
855
856     private void setLastWrappedLineCount(int lastWrappedLineCount) {
857         this.lastWrappedLineCount = lastWrappedLineCount;
858     }
859
860     private void setLastCharCountForWrapAboveCalculation(int lastCharCountForWrapAboveCalculation) {
861         this.lastCharCountForWrapAboveCalculation = lastCharCountForWrapAboveCalculation;
862     }
863
864     public String JavaDoc toString() {
865         return lineStartList.toString();
866     }
867 }
868
Popular Tags