/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed 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 com.intellij.util.containers; import com.intellij.openapi.diagnostic.Logger; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.List; public class WeakReferenceArray<T> { private static final Logger LOG = Logger.getInstance("#com.intellij.util.containers.WeakReferenceArray"); static final int MINIMUM_CAPACITY = 5; private final ReferenceQueue<T> myQueue = new TReferenceQueue<T>(); private MyWeakReference[] myReferences; private int mySize = 0; private int myCorpseCounter = 0; public WeakReferenceArray() { this(MINIMUM_CAPACITY); } public WeakReferenceArray(int size) { myReferences = new MyWeakReference[size]; } public T remove(int index) { checkRange(index); T result = getImpl(index); removeReference(index); return result; } private void checkRange(int index) { if (index >= mySize) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize); } } public int getCorpseCount() { flushQueue(); return myCorpseCounter; } private void flushQueue() { Reference nextRef; while ((nextRef = myQueue.poll()) != null) { if (!(nextRef instanceof MyWeakReference)) continue; // With 1.4 sometimes queue contains other references ?!? MyWeakReference reference = (MyWeakReference)nextRef; reference.setNull(myReferences); myCorpseCounter++; } } public void add(T object) { ensureCapacity(mySize + 1); MyWeakReference.createAt(myReferences, mySize, object, myQueue); mySize++; } public void add(int index, T element) { ensureCapacity(mySize + 1); if (index < 0 || index > mySize) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize); } for (int i = mySize - 1; i >= index; i--) { MyWeakReference aliveReference = MyWeakReference.getFrom(myReferences, i); if (aliveReference != null) { aliveReference.putTo(myReferences, i + 1); } } MyWeakReference.createAt(myReferences, index, element, myQueue); mySize++; } private void ensureCapacity(int size) { if (size != 0 && myReferences.length < MINIMUM_CAPACITY) { growTo(Math.max(MINIMUM_CAPACITY, size)); return; } if (size <= myReferences.length) return; if (size - myReferences.length <= getCorpseCount()) { compress(-1); if (mySize < myReferences.length - 1) return; } int newCapacity = 2 * myReferences.length; growTo(newCapacity); } private void growTo(int newCapacity) { MyWeakReference[] references = new MyWeakReference[newCapacity]; System.arraycopy(myReferences, 0, references, 0, myReferences.length); myReferences = references; } public int size() { return mySize; } public int compress(int trackIndex) { if (getCorpseCount() == 0) return trackIndex; return doCompress(myReferences, trackIndex); } private int doCompress(MyWeakReference[] newReferences, int trackIndex) { myCorpseCounter = 0; int validIndex = 0; int newIndex = -1; boolean trackingDone = false; for (int i = nextValid(-1); i < size(); i = nextValid(i)) { if (!trackingDone) { if (i == trackIndex) { newIndex = validIndex; trackingDone = true; } if (i > trackIndex) { newIndex = -validIndex - 1; trackingDone = true; } } MyWeakReference aliveReference = MyWeakReference.getFrom(myReferences, i); if (validIndex < i) { performRemoveAt(validIndex); //myReferences[i] = null; } else { LOG.assertTrue(validIndex == i); } aliveReference.moveTo(myReferences, newReferences, validIndex); validIndex++; } if (newIndex == -1) newIndex = -validIndex - 1; for (int i = validIndex; i < mySize; i++) { performRemoveAt(i); } for (int i = validIndex; i < myReferences.length; i++) { LOG.assertTrue(myReferences[i] == null); } flushQueue(); mySize = validIndex; return newIndex; } private void performRemoveAt(int index) { if (removeReference(index)) { myCorpseCounter--; flushQueue(); if (myCorpseCounter < 0) LOG.error(String.valueOf(myCorpseCounter)); } } int nextValid(int index) { index++; while (index < size()) { if (getImpl(index) != null) return index; index++; } return size(); } private T getImpl(int index) { final MyWeakReference<T> reference = MyWeakReference.getFrom(myReferences, index); return reference == null ? null : reference.get(); } public int getCapacity() { return myReferences.length; } public T get(int index) { checkRange(index); return getImpl(index); } public int reduceCapacity(int trackIndex) { int aliveSize = getNotBuriedCount(); if (myReferences.length / 4 >= aliveSize) { MyWeakReference[] references = new MyWeakReference[aliveSize * 2]; int newIndex = doCompress(references, trackIndex); myReferences = references; return newIndex; } return trackIndex; } private int getNotBuriedCount() { flushQueue(); int counter = 0; for (MyWeakReference myReference : myReferences) { if (myReference != null) counter++; } return counter; } public int getAliveCount() { return size() - getCorpseCount(); } // For testing only WeakReference[] getReferences() { return myReferences; } boolean removeReference(int index) { MyWeakReference reference = MyWeakReference.getFrom(myReferences, index); return reference != null && reference.removeFrom(myReferences); } public void toStrongCollection(final List<T> result) { for (MyWeakReference reference : myReferences) { final T deref = reference != null ? (T)reference.get() : null; if (deref != null) { result.add(deref); } } } private static class MyWeakReference<E> extends WeakReference<E> { private int myIndex = -1; private MyWeakReference(E e, ReferenceQueue<E> referenceQueue) { super(e, referenceQueue); } public static <E> void createAt(MyWeakReference[] array, int index, E element, ReferenceQueue<E> queue) { new MyWeakReference<E>(element, queue).putTo(array, index); } public static <E> MyWeakReference<E> getFrom(MyWeakReference[] array, int index) { MyWeakReference<E> reference = array[index]; if (reference == null) { return null; } LOG.assertTrue(index == reference.myIndex); return reference; } private void putTo(MyWeakReference[] array, int index) { array[index] = this; myIndex = index; } public boolean removeFrom(MyWeakReference[] array) { LOG.assertTrue(array[myIndex] == this); clear(); array[myIndex] = null; myIndex = -1; return enqueue(); } public void moveTo(MyWeakReference[] fromArray, MyWeakReference[] toArray, int newIndex) { LOG.assertTrue(fromArray[myIndex] == this); fromArray[myIndex] = null; LOG.assertTrue(toArray[newIndex] == null); toArray[newIndex] = this; myIndex = newIndex; } public void setNull(MyWeakReference[] array) { LOG.assertTrue(get() == null); if (myIndex == -1) return; LOG.assertTrue(array[myIndex] == this); array[myIndex] = null; } } private static class TReferenceQueue<T> extends ReferenceQueue<T> { @Override public Reference<? extends T> poll() { Reference<? extends T> reference = super.poll(); return reference; } } }