/*******************************************************************************
* 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.cache;
import static java.util.Collections.binarySearch;
import static java.util.logging.Level.WARNING;
import static org.eclipse.sisu.peaberry.cache.AbstractServiceImport.MODIFIED;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.eclipse.sisu.peaberry.AttributeFilter;
import org.eclipse.sisu.peaberry.Export;
import org.eclipse.sisu.peaberry.Import;
import org.eclipse.sisu.peaberry.ServiceWatcher;
/**
* Keep track of imported, sortable services that provide a specific interface.
*
* @author mcculls@gmail.com (Stuart McCulloch)
*/
public abstract class AbstractServiceListener<T> {
private static final Logger LOGGER = Logger.getLogger(AbstractServiceListener.class.getName());
private final List<AbstractServiceImport<T>> imports;
private final List<ServiceWatcher<T>> watchers;
protected AbstractServiceListener() {
imports = new ArrayList<AbstractServiceImport<T>>(4);
watchers = new ArrayList<ServiceWatcher<T>>(2);
}
@SuppressWarnings("unchecked")
synchronized void addWatcher(final ServiceWatcher watcher) {
if (!watchers.contains(watcher) && watchers.add(watcher)) {
// report existing imports to the new watcher
for (final AbstractServiceImport i : imports) {
notifyWatcher(watcher, i);
}
}
}
synchronized void flush(final int targetGeneration) {
// flush any unused cached service instances
for (final AbstractServiceImport<T> i : imports) {
i.flush(targetGeneration);
}
}
protected final void insertService(final AbstractServiceImport<T> i) {
// find insertion point that maintains ordering
final int insertIndex = binarySearch(imports, i);
if (insertIndex < 0) {
// new object, must flip index
imports.add(~insertIndex, i);
// report new import to any watching watchers
for (final ServiceWatcher<T> w : watchers) {
notifyWatcher(w, i);
}
}
}
protected final void updateService(final AbstractServiceImport<T> i) {
// use linear search in case ranking has changed
final int index = imports.indexOf(i);
if (0 <= index) {
// keep existing instance as it might be in use
final AbstractServiceImport<T> orig = imports.get(index);
// need to re-order list?
if (orig.hasRankingChanged()) {
imports.remove(index);
imports.add(~binarySearch(imports, orig), orig);
}
// finally update any watchers
orig.notifyWatchers(MODIFIED);
} else {
// not seen before
insertService(i);
}
}
protected final void removeService(final Import<T> i) {
// use linear search in case ranking has changed
final int index = imports.indexOf(i);
if (0 <= index) {
// flush cache even if being used
imports.remove(index).invalidate();
}
}
@SuppressWarnings("unchecked")
private static void notifyWatcher(final ServiceWatcher watcher, final AbstractServiceImport i) {
try {
final Export export = watcher.add(i);
if (null != export) {
i.addWatcher(export);
}
} catch (final RuntimeException re) {
LOGGER.log(WARNING, "Exception in service watcher", re);
}
}
synchronized Import<T> findNextImport(final Import<T> prevImport, final AttributeFilter filter) {
if (imports.isEmpty()) {
return null;
}
if (null == prevImport && null == filter) {
return imports.get(0);
}
// estimate last position based on previous value and current list
return findNextImport(filter, null == prevImport ? ~0 : binarySearch(imports, prevImport));
}
private Import<T> findNextImport(final AttributeFilter filter, final int prevIndex) {
// may need to flip position if previous import is no longer in the list
for (int i = prevIndex < 0 ? ~prevIndex : prevIndex + 1; i < imports.size(); i++) {
// now do a linear search applying the given filter
final Import<T> nextImport = imports.get(i);
if (null == filter || filter.matches(nextImport.attributes())) {
return nextImport;
}
}
return null; // no matching service
}
protected abstract void start();
}