/******************************************************************************* * 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.eclipse; import static java.util.Collections.binarySearch; import static java.util.logging.Level.WARNING; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IRegistryEventListener; import org.eclipse.sisu.peaberry.AttributeFilter; import org.eclipse.sisu.peaberry.Export; import org.eclipse.sisu.peaberry.ServiceWatcher; /** * Keep track of imported Eclipse Extensions that provide a specific interface. * * @author mcculls@gmail.com (Stuart McCulloch) */ final class ExtensionListener implements IRegistryEventListener { private static final Logger LOGGER = Logger.getLogger(ExtensionListener.class.getName()); private final Class<?> clazz; private final String point; private final boolean aggregate; private long idCounter; private final List<ExtensionImport> imports; private final List<ServiceWatcher<Object>> watchers; ExtensionListener(final Class<?> clazz) { final ExtensionBean metadata = clazz.getAnnotation(ExtensionBean.class); this.clazz = clazz; // no annotation => use lower-case class as point id if (null == metadata || metadata.value().length() == 0) { point = clazz.getName().toLowerCase() + 's'; } else { point = metadata.value(); } // do we need to combine elements into a single bean? aggregate = null != metadata && metadata.aggregate(); imports = new ArrayList<ExtensionImport>(4); watchers = new ArrayList<ServiceWatcher<Object>>(2); } synchronized void start(final IExtensionRegistry registry) { final IExtensionPoint[] extensionPoints; // register listener first to avoid race condition if (Object.class == clazz || IConfigurationElement.class == clazz) { registry.addListener(this); extensionPoints = registry.getExtensionPoints(); } else { registry.addListener(this, point); extensionPoints = new IExtensionPoint[]{registry.getExtensionPoint(point)}; } // safety check in case there was no matching extension point if (extensionPoints.length == 0 || null == extensionPoints[0]) { return; } final Set<IConfigurationElement> ignore = getExistingConfigurationElements(); // retrieve any matching extensions for each point for (final IExtensionPoint p : extensionPoints) { for (final IExtension e : p.getExtensions()) { insertExtension(e, ignore); } } } public synchronized void added(final IExtension[] extensions) { final Set<IConfigurationElement> ignore = getExistingConfigurationElements(); // each extension can have many configs for (final IExtension e : extensions) { insertExtension(e, ignore); } } private Set<IConfigurationElement> getExistingConfigurationElements() { final Set<IConfigurationElement> elements = new HashSet<IConfigurationElement>(); for (final ExtensionImport i : imports) { elements.add(i.getConfigurationElement()); } return elements; } public synchronized void removed(final IExtension[] extensions) { final List<IExtension> candidates = Arrays.asList(extensions); for (final Iterator<ExtensionImport> i = imports.iterator(); i.hasNext();) { if (i.next().invalidate(candidates)) { i.remove(); } } } public void added(final IExtensionPoint[] points) {/* do nothing */} public void removed(final IExtensionPoint[] points) {/* do nothing */} @SuppressWarnings("unchecked") synchronized void addWatcher(final ServiceWatcher watcher) { if (!watchers.contains(watcher) && watchers.add(watcher)) { // report existing imports to the new watcher for (final ExtensionImport i : imports) { notifyWatcher(watcher, i); } } } private void insertExtension(final IExtension extension, final Set<IConfigurationElement> ignore) { final List<IConfigurationElement> candidates = new ArrayList<IConfigurationElement>(); if (aggregate) { candidates.add(new AggregatedExtension(extension)); } else { Collections.addAll(candidates, extension.getConfigurationElements()); } candidates.removeAll(ignore); // create an import for each major configuration element for (final IConfigurationElement config : candidates) { final ExtensionImport i = new ExtensionImport(++idCounter, config, clazz); // NOPMD imports.add(i); // report the new import to any watching watchers for (final ServiceWatcher<Object> w : watchers) { notifyWatcher(w, i); } } } @SuppressWarnings("unchecked") private static void notifyWatcher(final ServiceWatcher watcher, final ExtensionImport 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 ExtensionImport findNextImport(final ExtensionImport 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 ExtensionImport 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 ExtensionImport nextImport = imports.get(i); if (null == filter || filter.matches(nextImport.attributes())) { return nextImport; } } return null; // no matching extension } }