/**
* $Id: ClassDataCacher.java 129 2014-03-18 23:25:36Z azeckoski $
* $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/ClassDataCacher.java $
* FieldUtils.java - genericdao - May 19, 2008 10:10:15 PM - azeckoski
**************************************************************************
* Copyright (c) 2008 Aaron Zeckoski
* Licensed under the Apache License, Version 2.0
*
* A copy of the Apache License has been included in this
* distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) (aaron @ caret.cam.ac.uk)
*/
package org.azeckoski.reflectutils;
import org.azeckoski.reflectutils.ClassFields.FieldFindMode;
import org.azeckoski.reflectutils.refmap.ReferenceMap;
import org.azeckoski.reflectutils.refmap.ReferenceType;
import java.lang.ref.SoftReference;
import java.util.Map;
/**
* Class which provides access to the analysis objects and the cached reflection data
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
public class ClassDataCacher {
// CONSTRUCTORS
/**
* default constructor - protected
*/
protected ClassDataCacher() {
this( null, null );
}
/**
* Construct and specify a mode for looking up fields which does not match the default: {@link FieldFindMode#HYBRID}
* @param fieldFindMode the mode when looking up fields in classes
* <br/>
* <b>WARNING:</b> if you don't need this control then just use the {@link #getInstance()} method to get this
*/
public ClassDataCacher(FieldFindMode fieldFindMode) {
this( fieldFindMode, null );
}
/**
* Construct and specify the field finding mode and your own cache when caching class data, must implement the standard map interface but
* only the following methods are required:<br/>
* {@link Map#clear()}, {@link Map#size()}, {@link Map#put(Object, Object)}, {@link Map#get(Object)} <br/>
* <br/>
* <b>WARNING:</b> if you don't need this control then just use the {@link #getInstance()} method to get this
*
* @param fieldFindMode the mode when looking up fields in classes (null for default of {@link FieldFindMode#HYBRID})
* @param reflectionCache a map implementation to use as the cache mechanism (null to use internal)
*/
@SuppressWarnings("unchecked")
public ClassDataCacher(FieldFindMode fieldFindMode, Map<Class<?>, ClassFields> reflectionCache) {
setFieldFindMode(fieldFindMode);
setReflectionCache(reflectionCache);
ClassDataCacher.setInstance(this);
}
// class fields
protected FieldFindMode fieldFindMode = FieldFindMode.HYBRID;
/**
* Set the mode used to find fields on classes (default {@link FieldFindMode#HYBRID}) <br/>
* <b>WARNING</b>: changing modes will clear the existing cache
*
* @param fieldFindMode see FieldFindMode enum for details
* @see FieldFindMode
*/
public void setFieldFindMode(FieldFindMode fieldFindMode) {
if (fieldFindMode == null) {
fieldFindMode = FieldFindMode.HYBRID;
}
if (! this.fieldFindMode.equals(fieldFindMode)) {
// need to clear the cache if we change the mode
getReflectionCache().clear();
}
this.fieldFindMode = fieldFindMode;
}
public FieldFindMode getFieldFindMode() {
return fieldFindMode;
}
protected boolean includeClassField = false;
/**
* Setting to determine if the result of "getClass()" should be included in the reflection data <br/>
* <b>WARNING</b>: changing this will clear the existing cache
*
* @param includeClassField if true then getClass() will be treated as a readable field called "class", default is false
*/
public void setIncludeClassField(boolean includeClassField) {
if (this.includeClassField != includeClassField) {
// need to clear the cache if we change this
getReflectionCache().clear();
}
this.includeClassField = includeClassField;
}
public boolean isIncludeClassField() {
return includeClassField;
}
public int lookups = 0;
public int cacheHits = 0;
public int cacheMisses = 0;
@SuppressWarnings("unchecked")
protected Map<Class<?>, ClassFields> reflectionCache = null;
@SuppressWarnings("unchecked")
protected Map<Class<?>, ClassFields> getReflectionCache() {
if (reflectionCache == null) {
// internally we are using the ReferenceMap (from the Guice codebase)
// modeled after the groovy reflection caching (weak -> soft)
reflectionCache = new ReferenceMap<Class<?>,ClassFields>(ReferenceType.WEAK, ReferenceType.SOFT);
}
return reflectionCache;
}
/**
* Set the cache to be used for holding the reflection data,
* this allows control over where the reflection caches are stored,
* this should store the data in a way that it will not hold open the classloader the class comes from <br/>
* Note that you can set this to a map implementation which does not store anything to disable caching if you like
*
* @param reflectionCache a cache for holding class cache data (implements map), null to use the default internal cache
*/
@SuppressWarnings("unchecked")
public void setReflectionCache(Map<Class<?>, ClassFields> reflectionCache) {
if (reflectionCache != null) {
this.reflectionCache.clear();
this.reflectionCache = reflectionCache;
} else {
getReflectionCache();
}
}
/**
* Get the class fields analysis of a class which contains information about that class and its fields,
* includes annotations, fields/properties, etc. packaged in a way which makes the data easy to get to,
* use the {@link ClassData} object to get to the more raw data
*
* @param <T>
* @param cls any {@link Class}
* @return the ClassFields analysis object which contains the information about this class
*/
public <T> ClassFields<T> getClassFields(Class<T> cls) {
return getClassFields(cls, this.fieldFindMode);
}
/**
* Get the class fields analysis of a class which contains information about that class and its fields,
* includes annotations, fields/properties, etc. packaged in a way which makes the data easy to get to,
* use the {@link ClassData} object to get to the more raw data
*
* @param <T>
* @param cls any {@link Class}
* @param mode (optional) mode for searching the class for fields, default HYBRID
* @return the ClassFields analysis object which contains the information about this class
*/
@SuppressWarnings("unchecked")
public <T> ClassFields<T> getClassFields(Class<T> cls, FieldFindMode mode) {
if (cls == null) {
throw new IllegalArgumentException("cls (type) cannot be null");
}
if (mode == null) {
if (this.fieldFindMode == null) {
mode = FieldFindMode.HYBRID; // default
} else {
mode = this.fieldFindMode;
}
}
lookups++;
ClassFields<T> cf = getReflectionCache().get(cls);
if (cf != null && !mode.equals(cf.getFieldFindMode())) {
cf = null;
}
if (cf == null) {
// make new and put in cache
cf = new ClassFields<T>(cls, mode, false, this.includeClassField);
getReflectionCache().put(cls, cf);
cacheMisses++;
} else {
cacheHits++;
}
return cf;
}
/**
* Convenience Method: <br/>
* Gets the class data object which contains information about this class,
* will retrieve this from the class data cache if available or generate it if not<br/>
* This is also available from the {@link ClassFields} object
*
* @param <T>
* @param cls any {@link Class}
* @return the class data cache object (contains reflected data from this class)
*/
public <T> ClassData<T> getClassData(Class<T> cls) {
ClassFields<T> cf = getClassFields(cls);
return cf.getClassData();
}
/**
* Convenience Method: <br/>
* Analyze an object and produce an object which contains information about it and its fields,
* see {@link ClassDataCacher#getClassData(Class)}
*
* @param <T>
* @param obj any {@link Object}
* @return the ClassFields analysis object which contains the information about this objects class
* @throws IllegalArgumentException if obj is null
*/
@SuppressWarnings("unchecked")
public <T> ClassFields<T> getClassFieldsFromObject(Object obj) {
if (obj == null) {
throw new IllegalArgumentException("obj cannot be null");
}
Class<T> cls = (Class<T>) obj.getClass();
return getClassFields(cls);
}
/**
* Convenience Method: <br/>
* Gets the class data object which contains information about this objects class,
* will retrieve this from the class data cache if available or generate it if not<br/>
* This is also available from the {@link ClassFields} object
*
* @param <T>
* @param obj any {@link Object}
* @return the raw ClassData cache object which contains reflection data about this objects class
* @throws IllegalArgumentException if obj is null
*/
public <T> ClassData<T> getClassData(Object obj) {
ClassFields<T> cf = getClassFieldsFromObject(obj);
return cf.getClassData();
}
/**
* Clears all cached objects
*/
public void clear() {
getReflectionCache().clear();
}
/**
* @return the size of the cache (number of cached {@link ClassFields} entries)
*/
public int size() {
return getReflectionCache().size();
}
@Override
public String toString() {
return "Cache::c="+ClassDataCacher.timesCreated+":s="+singleton+":fieldMode="+fieldFindMode+":lookups="+lookups+"::cache:hits="+cacheHits+":misses="+cacheMisses+":size="+size()+":singleton="+singleton;
}
// STATIC access
protected static SoftReference<ClassDataCacher> instanceStorage;
/**
* Get a singleton instance of this class to work with (stored statically) <br/>
* <b>WARNING</b>: do not hold onto this object or cache it yourself, call this method again if you need it again
* @return a singleton instance of this class
*/
public static ClassDataCacher getInstance() {
ClassDataCacher instance = (instanceStorage == null ? null : instanceStorage.get());
if (instance == null) {
instance = ClassDataCacher.setInstance(null);
}
return instance;
}
/**
* Set the singleton instance of the class which will be stored statically
* @param newInstance the instance to use as the singleton instance
*/
public static ClassDataCacher setInstance(ClassDataCacher newInstance) {
ClassDataCacher instance = newInstance;
if (instance == null) {
instance = new ClassDataCacher();
instance.singleton = true;
}
ClassDataCacher.timesCreated++;
instanceStorage = new SoftReference<ClassDataCacher>(instance);
return instance;
}
public static void clearInstance() {
instanceStorage.clear();
}
private static int timesCreated = 0;
public static int getTimesCreated() {
return timesCreated;
}
private boolean singleton = false;
public final boolean isSingleton() {
return singleton;
}
}