/**
* <copyright> Copyright (c) 2005, 2006, 2007, 2008 Springsite BV (The Netherlands) and others All rights
* reserved. This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html Contributors: Martin Taal </copyright> $Id:
* IdentifierCacheHandler.getInstance().java,v 1.5 2007/02/08 23:11:37 mtaal Exp $
*/
package org.eclipse.emf.teneo.hibernate.mapping.identifier;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.XMLResource;
/**
* Implements two maps for caching identifier and version information. Internally uses
* weakreferences and periodic purge actions to clean the maps.
*
* @author <a href="mailto:mtaal@elver.org">Martin Taal</a>
* @version $Revision: 1.18 $
*/
public class IdentifierCacheHandler {
/** The logger */
private static Log log = LogFactory.getLog(IdentifierCacheHandler.class);
/** At this count the maps will be purged for stale entries */
public static final int PURGE_TRESHOLD = 10000;
private static IdentifierCacheHandler instance = new IdentifierCacheHandler();
public static IdentifierCacheHandler getInstance() {
return instance;
}
public static void setInstance(IdentifierCacheHandler identifierCacheHandler) {
instance = identifierCacheHandler;
}
private Map<Key, Object> idMap = new ConcurrentHashMap<Key, Object>();
private int idModCount = 0;
private Map<Key, Object> versionMap = new ConcurrentHashMap<Key, Object>();
private int versionModCount = 0;
/** Clear the identifier cache */
public void clear() {
idMap.clear();
idModCount = 0;
versionMap.clear();
versionModCount = 0;
}
public Map<Key, Object> getIdMap() {
return idMap;
}
public Map<Key, Object> getVersionMap() {
return versionMap;
}
public void purgeMaps() {
purgeIDMap();
purgeVersionMap();
}
/** Get an identifier from the cache */
public Object getID(Object obj) {
final Object id = idMap.get(new Key(obj));
if (id == null) {
log.debug("ID for object " + obj.getClass().getName() + " not found in id cache");
return null;
}
if (id instanceof WeakReference<?>) {
return ((WeakReference<?>) id).get();
}
return id;
}
/** Set an identifier in the cache */
public void setID(Object obj, Object id) {
if (log.isDebugEnabled()) {
log.debug("Setting id: " + id + " for object " + obj.getClass().getName() + " in idcache ");
}
if (id == null) { // actually a remove of the id
idMap.remove(new Key(obj));
} else if (useWeakReference(id)) {
idMap.put(new Key(obj), new WeakReference<Object>(id));
} else {
idMap.put(new Key(obj), id);
}
// also set the id in the resource
// disabled for now
if (false && obj instanceof EObject) {
final EObject eobj = (EObject) obj;
final Resource res = eobj.eResource();
if (res != null && res instanceof XMLResource) {
if (log.isDebugEnabled()) {
log.debug("Setting id " + id.toString() + " in resource " + res.getClass().getName());
}
((XMLResource) res).setID(eobj, id.toString());
}
}
idModCount++;
if (idModCount > getPurgeTreshold()) {
purgeIDMap();
}
}
/** Gets a version from the cache */
public Object getVersion(Object obj) {
final Object version = versionMap.get(new Key(obj));
if (version == null) {
return version;
}
return version;
}
private boolean useWeakReference(Object id) {
if (Number.class.isAssignableFrom(id.getClass())) {
return false;
}
if (String.class.isAssignableFrom(id.getClass())) {
return false;
}
return true;
}
protected int getPurgeTreshold() {
return PURGE_TRESHOLD;
}
/** Sets a version in the cache */
public void setVersion(Object obj, Object version) {
if (log.isDebugEnabled()) {
log.debug("Setting version: " + version + " for object " + obj.getClass().getName() + " in idcache ");
}
if (version == null) {
versionMap.remove(new Key(obj));
} else {
versionMap.put(new Key(obj), version);
}
versionModCount++;
if (versionModCount > getPurgeTreshold()) {
purgeVersionMap();
}
}
/** Purge the versionmap for stale entries */
private void purgeIDMap() {
purgeMap(idMap);
idModCount = 0;
}
/** Purge the versionmap for stale entries */
protected void purgeVersionMap() {
purgeMap(versionMap);
versionModCount = 0;
}
/** Purges the passed map for stale entries */
protected void purgeMap(Map<Key, Object> map) {
final Iterator<Key> it = map.keySet().iterator();
while (it.hasNext()) {
final Key key = it.next();
if (!key.isValid()) {
it.remove();
}
}
}
/** Dumps the idmap */
public void dumpID() {
dumpContents(idMap);
}
/** Dumps the content of the passed map */
private void dumpContents(Map<Key, Object> map) {
Iterator<Key> it = map.keySet().iterator();
while (it.hasNext()) {
Key key = it.next();
key.weakRef.get();
}
}
/**
* Own implementation of the key in the hashmap to override the equals method. Equality for this
* cache is real memory location equality
*/
protected static class Key {
/** The real object as a weakreference */
private final WeakReference<Object> weakRef;
/** The hashcode of the stored object */
private final int hashcode;
/** Constructor */
Key(Object keyObject) {
weakRef = new WeakReference<Object>(keyObject);
hashcode = keyObject.hashCode();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object arg0) {
assert (arg0 != null);
assert (arg0 instanceof Key);
final Key key0 = (Key) arg0;
final Object obj0 = key0.weakRef.get();
final Object obj1 = weakRef.get();
// weakreference already gone compare on keys itself
if (obj0 == null || obj1 == null) {
return this == key0;
}
// still present compare on values
// equals call should maybe also be done but goes wrong for
// featuremap entries
// which are equal if their values and featuremap are equal
// identifier and version caching are only usefull in case of object
// equality
// because it uses weak references and the first level cache of hb
// should
// ensure that only one instance of a certain object is present.
// There should always be one instance anyway in one session
// otherwise
// references between objects can be set wrong (or at least there is
// a great
// change that they go wrong).
return obj0 == obj1;
}
/** The hashcode of the enclosed object is returned */
@Override
public int hashCode() {
return hashcode;
}
/** Returns true if the weakReference is not yet gc'ed */
public boolean isValid() {
return weakRef.get() != null;
}
}
}