blob: d83b06d9936f824b0f67ae33502d7f6ff9699f48 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2013 Sonatype, Inc.
* 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:
* Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.inject;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.sisu.BeanEntry;
import com.google.inject.Binding;
/**
* Atomic cache mapping {@link Binding}s to {@link BeanEntry}s; optimized for common case of single entries.
* <p>
* Uses {@code ==} instead of {@code equals} to compare {@link Binding}s because we want referential equality.
*/
@SuppressWarnings( { "rawtypes", "unchecked" } )
final class BeanCache<Q extends Annotation, T>
extends AtomicReference<Object>
{
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final long serialVersionUID = 1L;
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private Map<Binding<T>, BeanEntry<Q, T>> readCache;
private volatile boolean mutated;
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* Atomically creates a new {@link BeanEntry} for the given {@link Binding} reference.
*
* @param qualifier The qualifier
* @param binding The binding
* @param rank The assigned rank
* @return Associated bean entry
*/
public BeanEntry<Q, T> create( final Q qualifier, final Binding<T> binding, final int rank )
{
LazyBeanEntry newBean;
Object o, n;
/*
* Compare-and-swap approach; avoids locking without missing any updates
*/
do
{
o = get();
if ( null == o )
{
// most common case: adding the one (and-only) entry
n = newBean = new LazyBeanEntry( qualifier, binding, rank );
}
else if ( o instanceof LazyBeanEntry )
{
final LazyBeanEntry oldBean = (LazyBeanEntry) o;
if ( binding == oldBean.binding )
{
return oldBean;
}
n = createMap( oldBean, newBean = new LazyBeanEntry( qualifier, binding, rank ) );
}
else
{
synchronized ( this )
{
final Map<Binding, LazyBeanEntry> map = (Map) o;
if ( null == ( newBean = map.get( binding ) ) )
{
map.put( binding, newBean = new LazyBeanEntry( qualifier, binding, rank ) );
mutated = true;
}
return newBean;
}
}
}
while ( !compareAndSet( o, n ) );
if ( n instanceof IdentityHashMap )
{
mutated = true; // entry was upgraded to map, enable readCache
}
return newBean;
}
/**
* @return Read-only snapshot of the cache
*/
public Map<Binding<T>, BeanEntry<Q, T>> flush()
{
if ( mutated )
{
synchronized ( this )
{
if ( mutated )
{
readCache = (Map) ( (IdentityHashMap) get() ).clone();
mutated = false;
}
}
}
return readCache; // NOSONAR see 'happens-before' condition above
}
/**
* Retrieves the {@link Binding} references currently associated with {@link BeanEntry}s.
*
* @return Associated bindings
*/
public Iterable<Binding<T>> bindings()
{
final Object o = get();
if ( null == o )
{
return Collections.EMPTY_SET;
}
else if ( o instanceof LazyBeanEntry )
{
return Collections.singleton( ( (LazyBeanEntry<?, T>) o ).binding );
}
synchronized ( this )
{
return new ArrayList( ( (Map<Binding, BeanEntry>) o ).keySet() );
}
}
/**
* Removes the {@link BeanEntry} associated with the given {@link Binding} reference.
*
* @param binding The binding
* @return Associated bean entry
*/
public BeanEntry<Q, T> remove( final Binding<T> binding )
{
LazyBeanEntry oldBean;
Object o, n;
/*
* Compare-and-swap approach; avoids locking without missing any updates
*/
do
{
o = get();
if ( null == o )
{
return null;
}
else if ( o instanceof LazyBeanEntry )
{
oldBean = (LazyBeanEntry) o;
if ( binding != oldBean.binding )
{
return null;
}
n = null; // clear single entry
}
else
{
synchronized ( this )
{
oldBean = ( (Map<?, LazyBeanEntry>) o ).remove( binding );
if ( null != oldBean )
{
mutated = true;
}
return oldBean;
}
}
}
while ( !compareAndSet( o, n ) );
return oldBean;
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
private static Map createMap( final LazyBeanEntry one, final LazyBeanEntry two )
{
final Map map = new IdentityHashMap( 10 );
map.put( one.binding, one );
map.put( two.binding, two );
return map;
}
}