All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.text.LineVector Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.openide.text;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Binary searchable list of document lines.
 * 
* Member lines are weakly held. *
* Replacement of Line.Set.whm which contains ALL the registered lines in a SINGLE BUCKET * (thank to DocumentLine.hashcode()) and is an inappropriate storage type for this * kind of information. * * @author Miloslav Metelka */ final class LineVector { // -J-Dorg.openide.text.LineVector.level=FINE private static final Logger LOG = Logger.getLogger(LineVector.class.getName()); private Ref[] refArray; /** * Index of gap inside lineRefs array. */ private int gapStart; /** * Size of the gap. */ private int gapLength; /** * Number of line refs that were garbage collected. Once it reaches emptyRefsThreshold * a task is run that removes empty refs from lineRefs array. * If empty refs is more than 1/32 of total ref count then the empty refs are removed */ private int disposedRefCount; /** * If true then something went wrong and the array of references is not sorted properly * and therefore binary search cannot be used and the array must be traversed sequentially. */ private boolean refArrayUnsorted; private Thread lockThread; private int lockDepth; /** * Line updater that should update lines once the current request completes. */ private List pendingLineUpdaters = new ArrayList(2); LineVector() { this.refArray = new Ref[4]; this.gapLength = this.refArray.length; } Line findOrCreateLine(int findLineIndex, LineCreator lineCreator) { lockCheckUpdate(); try { int last = refCount() - 1; int low = 0; int high = last; if (!refArrayUnsorted) { int lowLineIndex = -1; int highLineIndex = Integer.MAX_VALUE; while (low <= high) { int mid = (low + high) >>> 1; // mid in the binary search Ref ref = refArray[rawIndex(mid)]; Line line = ref.get(); if (line == null) { int index = mid - 1; while (index >= 0) { ref = refArray[rawIndex(index)]; line = ref.get(); if (line != null) { break; } index--; } } int lineIndex = (line != null) ? line.getLineNumber() : -1; if (lineIndex < lowLineIndex || lineIndex > highLineIndex) { // Array became unsorted if (LOG.isLoggable(Level.FINE)) { String msg = "!!!LineVector: ARRAY BECAME UNSORTED!!!\n " + toStringDetail() + " lineIndex=" + lineIndex + // NOI18N ", lowLineIndex=" + lowLineIndex + ", highLineIndex=" + highLineIndex + // NOI18N "\n low=" + low + ", high=" + high + ", mid=" + mid + "\n"; // NOI18N LOG.log(Level.INFO, msg, new Throwable()); } refArrayUnsorted = true; break; // Iterate again this time sequential search will be used } if (lineIndex < findLineIndex) { low = mid + 1; lowLineIndex = lineIndex; } else if (lineIndex > findLineIndex) { high = mid - 1; highLineIndex = lineIndex; } else { // line numbers equal return line; } } } if (refArrayUnsorted) { // Unsorted array => use sequential search for (; low <= last; low++) { Ref ref = refArray[rawIndex(low)]; Line line = ref.get(); if (line != null && line.getLineNumber() == findLineIndex) { return line; } } low = gapStart; // Insert anywhere since the array is no longer sorted } // Create line at index "low" return (lineCreator != null) ? addLine(low, lineCreator.createLine(findLineIndex)) : null; } finally { unlockCheckUpdate(); } } void updateLines(LineUpdater lineUpdater) { synchronized (this) { pendingLineUpdaters.add(lineUpdater); if (lockThread == null) { // No locker -> do synchronously now lockCheckUpdate(); // Lock to ensure no recursive locking would happen try { } finally { unlockCheckUpdate(); // Perform updateLinesCheck() } } // else: the locker will perform update lines upon unlockCheckUpdate() } } private void updateLinesCheck() { List lineUpdaters; synchronized (this) { if (pendingLineUpdaters.size() > 0) { lineUpdaters = new ArrayList(pendingLineUpdaters); pendingLineUpdaters.clear(); } else { lineUpdaters = null; } } if (lineUpdaters != null) { for (LineUpdater lineUpdater : lineUpdaters) { for (int rawIndex = 0; rawIndex < gapStart; rawIndex++) { Line line = refArray[rawIndex].get(); lineUpdater.updateLine(line); } for (int rawIndex = gapStart + gapLength; rawIndex < refArray.length; rawIndex++) { Line line = refArray[rawIndex].get(); lineUpdater.updateLine(line); } } } } List getLinesInRange(int startLineIndex, int endLineIndex) { lockCheckUpdate(); try { List lines = new ArrayList(); int last = refCount() - 1; int low = 0; int high = last; if (!refArrayUnsorted) { int lowLineIndex = -1; int highLineIndex = Integer.MAX_VALUE; while (low <= high) { int mid = (low + high) >>> 1; // mid in the binary search Ref ref = refArray[rawIndex(mid)]; Line line = ref.get(); if (line == null) { int index = mid - 1; while (index >= 0) { ref = refArray[rawIndex(index)]; line = ref.get(); if (line != null) { break; } index--; } } int lineIndex = (line != null) ? line.getLineNumber() : -1; if (lineIndex < lowLineIndex || lineIndex > highLineIndex) { // Array became unsorted refArrayUnsorted = true; if (LOG.isLoggable(Level.FINE)) { String msg = "!!!LineVector: ARRAY BECAME UNSORTED!!!\n " + toStringDetail() + " lineIndex=" + lineIndex + // NOI18N ", lowLineIndex=" + lowLineIndex + ", highLineIndex=" + highLineIndex + // NOI18N "\n low=" + low + ", high=" + high + ", mid=" + mid + "\n"; // NOI18N LOG.log(Level.INFO, msg, new Throwable()); } break; // Iterate again this time sequential search will be used } if (lineIndex < startLineIndex) { low = mid + 1; lowLineIndex = lineIndex; } else if (lineIndex > startLineIndex) { high = mid - 1; highLineIndex = lineIndex; } else { // line numbers equal -> find first one while (--mid >= 0) { ref = refArray[rawIndex(mid)]; line = ref.get(); if (line != null && line.getLineNumber() < startLineIndex) { break; } } low = mid + 1; break; } } if (!refArrayUnsorted) { for (; low <= last; low++) { Line line = refArray[rawIndex(low)].get(); if (line != null) { int lineIndex = line.getLineNumber(); if (startLineIndex <= lineIndex && lineIndex <= endLineIndex) { lines.add(line); } else { break; } } } } } if (refArrayUnsorted) { // Unsorted array => use sequential search for (; low <= last; low++) { Line line = refArray[rawIndex(low)].get(); if (line != null) { int lineIndex = line.getLineNumber(); if (startLineIndex <= lineIndex && lineIndex <= endLineIndex) { lines.add(line); } } } } return lines; } finally { unlockCheckUpdate(); } } private Line addLine(int index, Line line) { moveGap(index); if (gapLength == 0) { reallocate((refArray.length + 8) >> 2); } refArray[gapStart++] = new Ref(line); gapLength--; return line; } private int refCount() { return refArray.length - gapLength; } private int rawIndex(int index) { return (index < gapStart) ? index : index + gapLength; } private void moveGap(int index) { // No need to clear the no-longer occupied space in refs array after arraycopy() // since these are only refs still present in the array and in the end removeEmptyRefsLockAcquired() will clean them if (index <= gapStart) { // move gap down int moveSize = gapStart - index; System.arraycopy(refArray, index, refArray, gapStart + gapLength - moveSize, moveSize); } else { // above gap int moveSize = index - gapStart; System.arraycopy(refArray, gapStart + gapLength, refArray, gapStart, moveSize); } gapStart = index; } synchronized void refGC() { disposedRefCount++; } private void checkRemoveEmptyRefs() { int cnt; synchronized (this) { cnt = disposedRefCount; } if (cnt > 4 && cnt > (refCount() >>> 3)) { removeEmptyRefs(); } } private void removeEmptyRefs() { int rawIndex = 0; int validIndex = 0; int emptyCount = 0; int gapEnd = gapStart + gapLength; // Only retain refs with valid lines while (rawIndex < gapStart) { Ref ref = refArray[rawIndex]; if (ref.get() != null) { if (rawIndex != validIndex) { refArray[validIndex] = ref; } validIndex++; } else { emptyCount++; } rawIndex++; } gapStart = validIndex; // Go back from end till gap end rawIndex = refArray.length; int topValidIndex = rawIndex; // validIndex points to first valid ref above gap while (--rawIndex >= gapEnd) { Ref ref = refArray[rawIndex]; if (ref.get() != null) { if (rawIndex != --topValidIndex) { refArray[topValidIndex] = ref; } } else { emptyCount++; } } int newGapLength = topValidIndex - gapStart; gapLength = newGapLength; // Clear the area between valid indices (also because moveGap() does not clear the stale areas) while (validIndex < topValidIndex) { refArray[validIndex++] = null; } if (LOG.isLoggable(Level.FINE)) { LOG.fine("LineVector.removeDisposedRefsLockAcquired() refCount=" + refCount() + ", emptyCount=" + emptyCount + "\n"); } synchronized (this) { disposedRefCount -= emptyCount; } } private void reallocate(int newGapLength) { int gapEnd = gapStart + gapLength; int aboveGapLength = refArray.length - gapEnd; int newLength = gapStart + aboveGapLength + newGapLength; Ref[] newRefArray = new Ref[newLength]; System.arraycopy(refArray, 0, newRefArray, 0, gapStart); System.arraycopy(refArray, gapEnd, newRefArray, newLength - aboveGapLength, aboveGapLength); if (LOG.isLoggable(Level.FINE)) { LOG.fine("LineVector.reallocate() from refArray.length=" + refArray.length + " to newLength=" + newLength + "\n"); } // gapStart is same gapLength = newGapLength; refArray = newRefArray; } private void lockCheckUpdate() { lock(); checkRemoveEmptyRefs(); } private synchronized void lock() { Thread currentThread = Thread.currentThread(); while (lockThread != null && currentThread != lockThread) { try { wait(); } catch (InterruptedException e) { throw new Error("Interrupted attempt to aquire lock"); } } if (lockThread != null) { // Recursive lock throw new IllegalStateException("Recursive line vector locking prohibited. LineVector: " + this); } lockThread = currentThread; lockDepth++; } private void unlockCheckUpdate() { // Check pending lines update updateLinesCheck(); unlock(); } private synchronized void unlock() { lockDepth--; if (lockDepth == 0) { lockThread = null; notifyAll(); } } @Override public String toString() { return "refArray.length=" + refArray.length + ", gapStart=" + gapStart + ", gapLength=" + gapLength + // NOI18N ", disposedRefCount=" + disposedRefCount + ", activeRefCount=" + (refCount()-disposedRefCount) + "\n refArrayUnsorted=" + refArrayUnsorted + // NOI18N ", lockThread=" + lockThread + ", lockDepth=" + lockDepth + // NOI18N ", pendingLineUpdaters=" + pendingLineUpdaters; // NOI18N } private String toStringDetail() { StringBuilder sb = new StringBuilder(256); lock(); try { sb.append(this.toString()).append('\n'); for (int i = 0; i < refCount(); i++) { Ref ref = refArray[rawIndex(i)]; Line line = ref.get(); sb.append("[").append(i).append("]:\t").append(line).append('\n'); } } finally { unlock(); } return sb.toString(); } private final class Ref extends WeakReference implements Runnable { public Ref(Line line) { super(line, org.openide.util.BaseUtilities.activeReferenceQueue()); // The queue calls run() when unreachable } @Override public void run() { refGC(); } } interface LineCreator { Line createLine(int line); } interface LineUpdater { void updateLine(Line line); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy