PageRenderTime 286ms CodeModel.GetById 189ms app.highlight 60ms RepoModel.GetById 20ms app.codeStats 1ms

/jEdit/tags/jedit-4-0-pre5/org/gjt/sp/jedit/buffer/OffsetManager.java

#
Java | 629 lines | 429 code | 77 blank | 123 comment | 89 complexity | 87a9c314bdd304d59d9b9cbd6ce24c7c MD5 | raw file
  1/*
  2 * OffsetManager.java - Manages line info, line start offsets, positions
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2001 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit.buffer;
 24
 25//{{{ Imports
 26import javax.swing.text.*;
 27import org.gjt.sp.jedit.syntax.*;
 28import org.gjt.sp.jedit.Buffer;
 29import org.gjt.sp.util.IntegerArray;
 30import org.gjt.sp.util.Log;
 31//}}}
 32
 33/**
 34 * A class internal to jEdit's document model. You should not use it
 35 * directly. To improve performance, none of the methods in this class
 36 * check for out of bounds access, nor are they thread-safe. The
 37 * <code>Buffer</code> class, through which these methods must be
 38 * called through, implements such protection.
 39 *
 40 * @author Slava Pestov
 41 * @version $Id: OffsetManager.java 3998 2002-01-28 04:20:54Z spestov $
 42 * @since jEdit 4.0pre1
 43 */
 44public class OffsetManager
 45{
 46	//{{{ OffsetManager constructor
 47	public OffsetManager(Buffer buffer)
 48	{
 49		this.buffer = buffer;
 50
 51		lineInfo = new long[1];
 52		// make first line visible by default
 53		lineInfo[0] = 1L | (0xffL << VISIBLE_SHIFT);
 54		lineContext = new TokenMarker.LineContext[1];
 55		lineCount = 1;
 56
 57		positions = new PosBottomHalf[100];
 58
 59		virtualLineCounts = new int[8];
 60		for(int i = 0; i < 8; i++)
 61			virtualLineCounts[i] = 1;
 62	} //}}}
 63
 64	//{{{ getLineCount() method
 65	public final int getLineCount()
 66	{
 67		return lineCount;
 68	} //}}}
 69
 70	//{{{ getVirtualLineCount() method
 71	public final int getVirtualLineCount(int index)
 72	{
 73		return virtualLineCounts[index];
 74	} //}}}
 75
 76	//{{{ setVirtualLineCount() method
 77	public final void setVirtualLineCount(int index, int lineCount)
 78	{
 79		virtualLineCounts[index] = lineCount;
 80	} //}}}
 81
 82	//{{{ getLineOfOffset() method
 83	public int getLineOfOffset(int offset)
 84	{
 85		int start = 0;
 86		int end = lineCount - 1;
 87
 88		for(;;)
 89		{
 90			switch(end - start)
 91			{
 92			case 0:
 93				if(getLineEndOffset(start) <= offset)
 94					return start + 1;
 95				else
 96					return start;
 97			case 1:
 98				if(getLineEndOffset(start) <= offset)
 99				{
100					if(getLineEndOffset(end) <= offset)
101						return end + 1;
102					else
103						return end;
104				}
105				else
106					return start;
107			default:
108				int pivot = (end + start) / 2;
109				int value = getLineEndOffset(pivot);
110				if(value == offset)
111					return pivot + 1;
112				else if(value < offset)
113					start = pivot + 1;
114				else
115					end = pivot - 1;
116				break;
117			}
118		}
119	} //}}}
120
121	//{{{ getLineEndOffset() method
122	public final int getLineEndOffset(int line)
123	{
124		return (int)(lineInfo[line] & END_MASK);
125	} //}}}
126
127	//{{{ isFoldLevelValid() method
128	public final boolean isFoldLevelValid(int line)
129	{
130		return (lineInfo[line] & FOLD_LEVEL_VALID_MASK) != 0;
131	} //}}}
132
133	//{{{ getFoldLevel() method
134	public final int getFoldLevel(int line)
135	{
136		return (int)((lineInfo[line] & FOLD_LEVEL_MASK)
137			>> FOLD_LEVEL_SHIFT);
138	} //}}}
139
140	//{{{ setFoldLevel() method
141	// Also sets 'fold level valid' flag
142	public final void setFoldLevel(int line, int level)
143	{
144		lineInfo[line] = ((lineInfo[line] & ~FOLD_LEVEL_MASK)
145			| ((long)level << FOLD_LEVEL_SHIFT)
146			| FOLD_LEVEL_VALID_MASK);
147	} //}}}
148
149	//{{{ isLineVisible() method
150	public final boolean isLineVisible(int line, int index)
151	{
152		long mask = 1L << (index + VISIBLE_SHIFT);
153		return (lineInfo[line] & mask) != 0;
154	} //}}}
155
156	//{{{ setLineVisible() method
157	public final void setLineVisible(int line, int index, boolean visible)
158	{
159		long mask = 1L << (index + VISIBLE_SHIFT);
160		if(visible)
161			lineInfo[line] = (lineInfo[line] | mask);
162		else
163			lineInfo[line] = (lineInfo[line] & ~mask);
164	} //}}}
165
166	//{{{ isLineContextValid() method
167	public final boolean isLineContextValid(int line)
168	{
169		return (lineInfo[line] & CONTEXT_VALID_MASK) != 0;
170	} //}}}
171
172	//{{{ getLineContext() method
173	public final TokenMarker.LineContext getLineContext(int line)
174	{
175		return lineContext[line];
176	} //}}}
177
178	//{{{ setLineContext() method
179	// Also sets 'context valid' to true
180	public final void setLineContext(int line, TokenMarker.LineContext context)
181	{
182		lineContext[line] = context;
183		lineInfo[line] |= CONTEXT_VALID_MASK;
184	} //}}}
185
186	//{{{ createPosition() method
187
188	// note: Buffer.createPosition() grabs a read lock, so the buffer
189	// will not change during this method. however, if two stops call
190	// it, there can be contention issues unless this method is
191	// synchronized.
192
193	// I could make Buffer.createPosition() grab a write lock, but then
194	// it would be necessary to implement grabbing write locks within
195	// read locks, since HyperSearch for example does everything inside
196	// a read lock.
197	public synchronized Position createPosition(int offset)
198	{
199		PosBottomHalf bh = null;
200
201		for(int i = 0; i < positionCount; i++)
202		{
203			PosBottomHalf _bh = positions[i];
204			if(_bh.offset == offset)
205			{
206				bh = _bh;
207				break;
208			}
209			else if(_bh.offset > offset)
210			{
211				bh = new PosBottomHalf(offset);
212				growPositionArray();
213				System.arraycopy(positions,i,positions,i+1,
214					positionCount - i);
215				positionCount++;
216				positions[i] = bh;
217				break;
218			}
219		}
220
221		if(bh == null)
222		{
223			bh = new PosBottomHalf(offset);
224			growPositionArray();
225			positions[positionCount++] = bh;
226		}
227
228		return new PosTopHalf(bh);
229	} //}}}
230
231	//{{{ expandFolds() method
232	/**
233	 * Like <code>FoldVisibilityManager.expandFolds()</code>, but does
234	 * it for all fold visibility managers viewing this buffer. Should
235	 * only be called after loading.
236	 */
237	public void expandFolds(int foldLevel)
238	{
239		int newVirtualLineCount = 0;
240		foldLevel = (foldLevel - 1) * buffer.getIndentSize() + 1;
241
242		/* this ensures that the first line is always visible */
243		boolean seenVisibleLine = false;
244
245		for(int i = 0; i < getLineCount(); i++)
246		{
247			if(!seenVisibleLine || buffer.getFoldLevel(i) < foldLevel)
248			{
249				seenVisibleLine = true;
250				// Since only called on load, it already has
251				// the VISIBLE_MASK set
252				//lineInfo[i] |= VISIBLE_MASK;
253				newVirtualLineCount++;
254			}
255			else
256				lineInfo[i] &= ~VISIBLE_MASK;
257		}
258
259		for(int i = 0; i < virtualLineCounts.length; i++)
260		{
261			virtualLineCounts[i] = newVirtualLineCount;
262		}
263	} //}}}
264
265	//{{{ contentInserted() method
266	public void contentInserted(int startLine, int offset,
267		int numLines, int length, IntegerArray endOffsets)
268	{
269		int endLine = startLine + numLines;
270
271		//{{{ Update line info and line context arrays
272		if(numLines > 0)
273		{
274			lineCount += numLines;
275
276			if(lineInfo.length <= lineCount)
277			{
278				long[] lineInfoN = new long[(lineCount + 1) * 2];
279				System.arraycopy(lineInfo,0,lineInfoN,0,
280						 lineInfo.length);
281				lineInfo = lineInfoN;
282
283				TokenMarker.LineContext[] lineContextN
284					= new TokenMarker.LineContext[(lineCount + 1) * 2];
285				System.arraycopy(lineContext,0,lineContextN,0,
286						 lineContext.length);
287				lineContext = lineContextN;
288			}
289
290			System.arraycopy(lineInfo,startLine,lineInfo,
291				endLine,lineCount - endLine);
292			System.arraycopy(lineContext,startLine,lineContext,
293				endLine,lineCount - endLine);
294
295			//{{{ Find fold start of this line
296			int foldLevel = buffer.getFoldLevel(startLine);
297			long visible = (0xffL << VISIBLE_SHIFT);
298			if(startLine != 0)
299			{
300				for(int i = startLine; i > 0; i--)
301				{
302					if(/* buffer.isFoldStart(i - 1)
303						&& */ buffer.getFoldLevel(i) <= foldLevel)
304					{
305						visible = (lineInfo[i] & VISIBLE_MASK);
306						break;
307					}
308				}
309			} //}}}
310
311			for(int i = 0; i < numLines; i++)
312			{
313				// need the line end offset to be in place
314				// for following fold level calculations
315				lineInfo[startLine + i] = ((offset + endOffsets.get(i) + 1)
316					& ~(FOLD_LEVEL_VALID_MASK | CONTEXT_VALID_MASK)
317					| visible);
318			}
319
320			//{{{ Unrolled
321			if((visible & (1L << (VISIBLE_SHIFT + 0))) != 0)
322				virtualLineCounts[0] += numLines;
323			if((visible & (1L << (VISIBLE_SHIFT + 1))) != 0)
324				virtualLineCounts[1] += numLines;
325			if((visible & (1L << (VISIBLE_SHIFT + 2))) != 0)
326				virtualLineCounts[2] += numLines;
327			if((visible & (1L << (VISIBLE_SHIFT + 3))) != 0)
328				virtualLineCounts[3] += numLines;
329			if((visible & (1L << (VISIBLE_SHIFT + 4))) != 0)
330				virtualLineCounts[4] += numLines;
331			if((visible & (1L << (VISIBLE_SHIFT + 5))) != 0)
332				virtualLineCounts[5] += numLines;
333			if((visible & (1L << (VISIBLE_SHIFT + 6))) != 0)
334				virtualLineCounts[6] += numLines;
335			if((visible & (1L << (VISIBLE_SHIFT + 7))) != 0)
336				virtualLineCounts[7] += numLines;
337			//}}}
338		} //}}}
339		//{{{ Update remaining line start offsets
340		for(int i = endLine; i < lineCount; i++)
341		{
342			setLineEndOffset(i,getLineEndOffset(i) + length);
343			lineInfo[i] &= ~(FOLD_LEVEL_VALID_MASK
344				| CONTEXT_VALID_MASK);
345		} //}}}
346
347		updatePositionsForInsert(offset,length);
348	} //}}}
349
350	//{{{ contentRemoved() method
351	public void contentRemoved(int startLine, int offset,
352		int numLines, int length)
353	{
354		//{{{ Update virtual line counts
355		for(int i = 0; i < numLines; i++)
356		{
357			long info = lineInfo[startLine + i];
358
359			// Unrolled for max efficency
360			if((info & (1L << (VISIBLE_SHIFT + 0))) != 0)
361				virtualLineCounts[0]--;
362			if((info & (1L << (VISIBLE_SHIFT + 1))) != 0)
363				virtualLineCounts[1]--;
364			if((info & (1L << (VISIBLE_SHIFT + 2))) != 0)
365				virtualLineCounts[2]--;
366			if((info & (1L << (VISIBLE_SHIFT + 3))) != 0)
367				virtualLineCounts[3]--;
368			if((info & (1L << (VISIBLE_SHIFT + 4))) != 0)
369				virtualLineCounts[4]--;
370			if((info & (1L << (VISIBLE_SHIFT + 5))) != 0)
371				virtualLineCounts[5]--;
372			if((info & (1L << (VISIBLE_SHIFT + 6))) != 0)
373				virtualLineCounts[6]--;
374			if((info & (1L << (VISIBLE_SHIFT + 7))) != 0)
375				virtualLineCounts[7]--;
376		} //}}}
377
378		//{{{ Update line info and line context arrays
379		if(numLines > 0)
380		{
381			lineCount -= numLines;
382			System.arraycopy(lineInfo,startLine + numLines,lineInfo,
383				startLine,lineCount - startLine);
384			System.arraycopy(lineContext,startLine + numLines,lineContext,
385				startLine,lineCount - startLine);
386		} //}}}
387
388		//{{{ Update remaining line start offsets
389		for(int i = startLine; i < lineCount; i++)
390		{
391			setLineEndOffset(i,getLineEndOffset(i) - length);
392			lineInfo[i] &= ~(FOLD_LEVEL_VALID_MASK
393				| CONTEXT_VALID_MASK);
394		} //}}}
395
396		updatePositionsForRemove(offset,length);
397	} //}}}
398
399	//{{{ linesChanged() method
400	public void linesChanged(int startLine, int numLines)
401	{
402		for(int i = 0; i < numLines; i++)
403		{
404			lineInfo[startLine + i] &= ~(FOLD_LEVEL_VALID_MASK
405				| CONTEXT_VALID_MASK);
406			lineContext[startLine + i] = null;
407		}
408	} //}}}
409
410	//{{{ Private members
411
412	/* {{{ Format of entires in line info array:
413	 * 0-31: end offset
414	 * 32-47: fold level
415	 * 48-55: visibility bit flags
416	 * 56: fold level valid flag
417	 * 57: context valid flag
418	 * 58-63: reserved
419	 *
420	 * Having all the info packed into a long is not very OO and makes the
421	 * code somewhat more complicated, but it saves a lot of memory.
422	 *
423	 * The new document model has just 12 bytes of overhead per line.
424	 * LineContext instances are now internalized, so only a few should
425	 * actually be in the heap.
426	 *
427	 * In the old document model there were 5 objects per line, for a
428	 * total of about 100 bytes, plus a cached token list, which used
429	 * another 100 or so bytes.
430	 * }}}*/
431	private static final long END_MASK = 0x00000000ffffffffL;
432	private static final long FOLD_LEVEL_MASK = 0x0000ffff00000000L;
433	private static final int FOLD_LEVEL_SHIFT = 32;
434	private static final long VISIBLE_MASK = 0x00ff000000000000L;
435	private static final int VISIBLE_SHIFT = 48;
436	private static final long FOLD_LEVEL_VALID_MASK = (1L<<56);
437	private static final long CONTEXT_VALID_MASK = (1L<<57);
438
439	//{{{ Instance variables
440	private Buffer buffer;
441	private long[] lineInfo;
442	private TokenMarker.LineContext[] lineContext;
443
444	private int lineCount;
445
446	private PosBottomHalf[] positions;
447	private int positionCount;
448
449	private int[] virtualLineCounts;
450	//}}}
451
452	//{{{ setLineEndOffset() method
453	private final void setLineEndOffset(int line, int end)
454	{
455		lineInfo[line] = ((lineInfo[line] & ~END_MASK) | end);
456	} //}}}
457
458	//{{{ growPositionArray() method
459	private void growPositionArray()
460	{
461		if(positions.length < positionCount + 1)
462		{
463			PosBottomHalf[] newPositions = new PosBottomHalf[
464				(positionCount + 1) * 2];
465			System.arraycopy(positions,0,newPositions,0,positionCount);
466			positions = newPositions;
467		}
468	} //}}}
469
470	//{{{ removePosition() method
471	private synchronized void removePosition(PosBottomHalf bh)
472	{
473		int index = -1;
474
475		for(int i = 0; i < positionCount; i++)
476		{
477			if(positions[i] == bh)
478			{
479				index = i;
480				break;
481			}
482		}
483
484		System.arraycopy(positions,index + 1,positions,index,
485			positionCount - index - 1);
486		positions[--positionCount] = null;
487	} //}}}
488
489	//{{{ updatePositionsForInsert() method
490	private void updatePositionsForInsert(int offset, int length)
491	{
492		if(positionCount == 0)
493			return;
494
495		int start = getPositionAtOffset(offset);
496
497		for(int i = start; i < positionCount; i++)
498		{
499			PosBottomHalf bh = positions[i];
500			if(bh.offset < offset)
501				Log.log(Log.ERROR,this,"Screwed up: " + bh.offset);
502			else
503				bh.offset += length;
504		}
505	} //}}}
506
507	//{{{ updatePositionsForRemove() method
508	private void updatePositionsForRemove(int offset, int length)
509	{
510		if(positionCount == 0)
511			return;
512
513		int start = getPositionAtOffset(offset);
514
515		for(int i = start; i < positionCount; i++)
516		{
517			PosBottomHalf bh = positions[i];
518			if(bh.offset < offset)
519				Log.log(Log.ERROR,this,"Screwed up: " + bh.offset);
520			else if(bh.offset < offset + length)
521				bh.offset = offset;
522			else
523				bh.offset -= length;
524		}
525	} //}}}
526
527	//{{{ getPositionAtOffset() method
528	private int getPositionAtOffset(int offset)
529	{
530		int start = 0;
531		int end = positionCount - 1;
532
533		PosBottomHalf bh;
534
535loop:		for(;;)
536		{
537			switch(end - start)
538			{
539			case 0:
540				bh = positions[start];
541				if(bh.offset < offset)
542					start++;
543				break loop;
544			case 1:
545				bh = positions[end];
546				if(bh.offset < offset)
547				{
548					start = end + 1;
549				}
550				else
551				{
552					bh = positions[start];
553					if(bh.offset < offset)
554					{
555						start++;
556					}
557				}
558				break loop;
559			default:
560				int pivot = (start + end) / 2;
561				bh = positions[pivot];
562				if(bh.offset > offset)
563					end = pivot - 1;
564				else
565					start = pivot + 1;
566				break;
567			}
568		}
569
570		return start;
571	} //}}}
572
573	//}}}
574
575	//{{{ Inner classes
576
577	//{{{ PosTopHalf class
578	static class PosTopHalf implements Position
579	{
580		PosBottomHalf bh;
581
582		//{{{ PosTopHalf constructor
583		PosTopHalf(PosBottomHalf bh)
584		{
585			this.bh = bh;
586			bh.ref();
587		} //}}}
588
589		//{{{ getOffset() method
590		public int getOffset()
591		{
592			return bh.offset;
593		} //}}}
594
595		//{{{ finalize() method
596		public void finalize()
597		{
598			bh.unref();
599		} //}}}
600	} //}}}
601
602	//{{{ PosBottomHalf class
603	class PosBottomHalf
604	{
605		int offset;
606		int ref;
607
608		//{{{ PosBottomHalf constructor
609		PosBottomHalf(int offset)
610		{
611			this.offset = offset;
612		} //}}}
613
614		//{{{ ref() method
615		void ref()
616		{
617			ref++;
618		} //}}}
619
620		//{{{ unref() method
621		void unref()
622		{
623			if(--ref == 0)
624				removePosition(this);
625		} //}}}
626	} //}}}
627
628	//}}}
629}