/*
* @copyright 2012 Philip Warner
* @license GNU General Public License
*
* This file is part of Book Catalogue.
*
* Book Catalogue is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Book Catalogue is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>.
*/
package com.eleybourn.bookcatalogue.utils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQuery;
import com.eleybourn.bookcatalogue.database.DbSync.SynchronizedCursor;
import com.eleybourn.bookcatalogue.database.DbSync.Synchronizer;
/**
* DEBUG CLASS to help com.eleybourn.bookcatalogue.debug cursor leakage.
*
* Set the static variable DEBUG_TRACKED_CURSOR to 'false' to make most of the code a NOP.
*
* By using TrackedCursorFactory it is possible to use this class to analyze when and
* where cursors are being allocated, and whether they are being deallocated in a timely
* fashion.
*
* @author Philip Warner
*/
public class TrackedCursor extends SynchronizedCursor {
/** Set to TRUE to actually track cursors. Otherwise, most code is optimized out. */
private static final boolean DEBUG_TRACKED_CURSOR = false;
/* Static Data */
/* =========== */
/** Used as a collection of known cursors */
private static HashSet<WeakReference<TrackedCursor>> mCursors = new HashSet<WeakReference<TrackedCursor>>();
/** Global counter for unique cursor IDs */
private static Long mIdCounter = 0L;
/* Instance Data */
/* ============= */
/** ID of the current cursor */
private final Long mId;
/** We record a stack track when a cursor is created. */
private StackTraceElement[] mStackTrace;
/** Weak reference to this object, used in cursor collection */
private WeakReference<TrackedCursor> mWeakRef;
/** Already closed */
private boolean mIsClosedFlg = false;
/** Debug counter */
public static Integer mInstanceCount = 0;
/**
* Constructor.
*
* @param db
* @param driver
* @param editTable
* @param query
*/
public TrackedCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, SQLiteQuery query, Synchronizer sync) {
super(db, driver, editTable, query, sync);
if (DEBUG_TRACKED_CURSOR) {
synchronized(mInstanceCount) {
mInstanceCount++;
System.out.println("Cursor instances: " + mInstanceCount);
}
// Record who called us. It's only from about the 7th element that matters.
mStackTrace = Thread.currentThread().getStackTrace();
// Get the next ID
synchronized(mIdCounter)
{
mId = ++mIdCounter;
}
// Save this cursor in the collection
synchronized(mCursors) {
mWeakRef = new WeakReference<TrackedCursor>(this);
mCursors.add(mWeakRef);
}
} else {
mId = 0L;
}
}
/**
* Remove from collection on close.
*/
@Override
public void close() {
super.close();
if (DEBUG_TRACKED_CURSOR) {
if (!mIsClosedFlg) {
synchronized(mInstanceCount) {
mInstanceCount--;
System.out.println("Cursor instances: " + mInstanceCount);
}
if (mWeakRef != null)
synchronized(mCursors) {
mCursors.remove(mWeakRef);
mWeakRef.clear();
mWeakRef = null;
}
mIsClosedFlg = true;
}
}
}
/**
* Finalizer that does sanity check. Setting a break here can catch the exact moment that
* a cursor is deleted before being closed.
*/
@Override
public void finalize() {
if (DEBUG_TRACKED_CURSOR) {
if (mWeakRef != null) {
// This is a cursor that is being deleted before it is closed.
// Setting a break here is sometimes useful.
synchronized(mCursors) {
mCursors.remove(mWeakRef);
mWeakRef.clear();
mWeakRef = null;
}
}
}
super.finalize();
}
/**
* Get the stack trace recorded when cursor created
* @return
*/
public StackTraceElement[] getStackTrace() {
return mStackTrace;
}
/**
* Get the ID of this cursor
* @return
*/
final public long getCursorId() {
return mId;
}
/**
* Get the total number of cursors that have not called close(). This is subtly
* different from the list of open cursors because non-referenced cursors may
* have been deleted and the finalizer not called.
*
* @return
*/
public static long getCursorCountApproximate() {
long count = 0;
if (DEBUG_TRACKED_CURSOR) {
synchronized(mCursors) {
count = mCursors.size();
}
}
return count;
}
/**
* Get the total number of open cursors; verifies that existing weak refs are valid
* and removes from collection if not.
*
* Note: This is not a *cheap* operation.
*
* @return
*/
public static long getCursorCount() {
long count = 0;
if (DEBUG_TRACKED_CURSOR) {
ArrayList<WeakReference<TrackedCursor>> list = new ArrayList<WeakReference<TrackedCursor>>();
synchronized(mCursors) {
for(WeakReference<TrackedCursor> r : mCursors) {
TrackedCursor c = r.get();
if (c != null)
count++;
else
list.add(r);
}
for(WeakReference<TrackedCursor> r : list) {
mCursors.remove(r);
}
}
}
return count;
}
/**
* Dump all open cursors to System.out.
*/
public static void dumpCursors() {
if (DEBUG_TRACKED_CURSOR) {
for(TrackedCursor c : getCursors()) {
System.out.println("Cursor " + c.getCursorId());
for (StackTraceElement s : c.getStackTrace()) {
System.out.println(s.getFileName() + " Line " + s.getLineNumber() + " Method " + s.getMethodName());
}
}
}
}
/**
* Get a collection of open cursors at the current time.
*
* @return
*/
public static ArrayList<TrackedCursor> getCursors() {
ArrayList<TrackedCursor> list = new ArrayList<TrackedCursor>();
if (DEBUG_TRACKED_CURSOR) {
synchronized(mCursors) {
for(WeakReference<TrackedCursor> r : mCursors) {
TrackedCursor c = r.get();
if (c != null)
list.add(c);
}
}
}
return list;
}
}