/*******************************************************************************
* Copyright (c) 2008, 2014 Stuart McCulloch
* 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 - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.peaberry.internal;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.sisu.peaberry.Import;
/**
* Provide an {@link Import} that dynamically delegates to the best service but
* also tracks its use (even across multiple threads) so that unget() is always
* called on the same service handle as get() was originally.
*
* The solution below uses the same handle until no threads are actively using
* the injected instance. This might keep a service in use for a little longer
* than expected when there is heavy contention, but it doesn't require use of
* any thread locals or additional context stacks.
*
* @author mcculls@gmail.com (Stuart McCulloch)
*/
final class ConcurrentImport<T>
implements Import<T> {
private final Iterable<Import<T>> services;
private Import<T> service;
private T instance;
private int count;
ConcurrentImport(final Iterable<Import<T>> services) {
this.services = services;
}
// need barrier on entry...
public synchronized T get() {
count++;
if (null == service) {
// first valid service handle may appear at any time
final Iterator<Import<T>> i = services.iterator();
if (i.hasNext()) {
service = i.next();
instance = service.get(); // only called once
}
}
return instance;
}
public synchronized Map<String, ?> attributes() {
if (null == service) {
// no service in use, fall-back to dynamic query
final Iterator<Import<T>> i = services.iterator();
return i.hasNext() ? i.next().attributes() : null;
}
return service.attributes();
}
public synchronized void unget() {
// last thread to exit does the unget...
if (0 == --count && null != service) {
final Import<T> temp = service;
instance = null;
service = null;
temp.unget();
}
}
public synchronized boolean available() {
if (null == service) {
return services.iterator().hasNext();
}
return service.available();
}
}