/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) 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: * bstefanescu */ package org.eclipse.ecr.automation.core.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * A registry which is inheriting values from super keys. The super key * relation is defined by the derived classes by overriding * {@link #getSuperKeys(Object)} method. The registry is thread safe and is * optimized for lookups. A concurrent cache is dynamically updated when a * value is retrieved from a super entry. The cache is removed each time a * modification is made on the registry using {@link #put(Object, Object)} or * {@link #remove(Object)} methods. Thus, for maximum performance you need to * avoid modifying the registry after lookups were done: at application startup * build the registry, at runtime perform lookups, at shutdown remove entries. * The root key is passed in the constructor and is used to stop looking in * super entries. * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public abstract class SuperKeyedRegistry<K, V> { private static final Object NULL = new Object(); protected Map<K, V> registry; /** * the cache map used for lookups. Object is used for the value to be able * to insert NULL values. */ protected volatile ConcurrentMap<K, Object> lookup; /** * the lock used to update the registry */ private final Object lock = new Object(); public SuperKeyedRegistry() { registry = new HashMap<K, V>(); } public void put(K key, V value) { synchronized (lock) { registry.put(key, value); lookup = null; } } public V remove(K key) { V value; synchronized (lock) { value = registry.remove(key); lookup = null; } return value; } public void flushCache() { synchronized (lock) { lookup = null; } } protected abstract boolean isRoot(K key); protected abstract List<K> getSuperKeys(K key); /** * Override this in order to disable caching some specific keys. For * example when using java classes as keys you may want to avoid caching * proxy classes. The default is to return true. (cache is enabled) */ protected boolean isCachingEnabled(K key) { return true; } @SuppressWarnings("unchecked") public V get(K key) { Map<K, Object> _lookup = lookup; if (_lookup == null) { synchronized (lock) { lookup = new ConcurrentHashMap<K, Object>(registry); _lookup = lookup; } } Object v = _lookup.get(key); if (v == null && !isRoot(key)) { // System.out.println("cache missed: "+key); for (K sk : getSuperKeys(key)) { v = get(sk); if (v != null && v != NULL) { // we found what we need so abort scanning interfaces / // subclasses if (isCachingEnabled(sk)) { _lookup.put(key, v); // update cache return (V) v; } } else { if (isCachingEnabled(sk)) { if (v != null) { // add inherited binding _lookup.put(key, v); } else { _lookup.put(key, NULL); } } } } } return (V) (v == NULL ? null : v); } }