/******************************************************************************* * 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.unmodifiableMap; import static java.util.logging.Level.WARNING; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.InvalidRegistryObjectException; import org.eclipse.sisu.peaberry.Export; import org.eclipse.sisu.peaberry.Import; import org.eclipse.sisu.peaberry.ServiceUnavailableException; /** * {@link Import} implementation backed by an Eclipse Extension. * * @author mcculls@gmail.com (Stuart McCulloch) */ @SuppressWarnings("PMD.TooManyMethods") final class ExtensionImport implements Import<Object>, Comparable<ExtensionImport> { private static final Logger LOGGER = Logger.getLogger(ExtensionImport.class.getName()); private static final int INVALID = -1; private static final int DORMANT = 0; private static final int ACTIVE = 1; private final long id; // internal sequence number private final IConfigurationElement config; private final Class<?> clazz; private volatile Object instance; private volatile int state; private volatile Map<String, ?> attributes; private final List<Export<?>> watchers; ExtensionImport(final long id, final IConfigurationElement config, final Class<?> clazz) { this.id = id; this.config = config; this.clazz = clazz; watchers = new ArrayList<Export<?>>(2); } public Object get() { if (DORMANT == state) { synchronized (this) { if (DORMANT == state) { try { instance = ExtensionBeanFactory.newInstance(clazz, config); } catch (final RuntimeException re) { throw new ServiceUnavailableException(re); } finally { state = ACTIVE; } } } } return instance; } public Map<String, ?> attributes() { // perform lazy conversion if (null == attributes) { synchronized (this) { if (null == attributes) { attributes = collectAttributes(config); } } } return attributes; } public void unget() {/* do nothing */} public boolean available() { return config.isValid(); } /** * Protected from concurrent access by {@link ExtensionListener}. */ void addWatcher(final Export<?> export) { watchers.add(export); } IConfigurationElement getConfigurationElement() { return config; } /** * Protected from concurrent access by {@link ExtensionListener}. */ boolean invalidate(final Collection<IExtension> candidates) { try { if (!candidates.contains(config.getDeclaringExtension())) { return false; /* this doesn't belong to any candidate */ } } catch (final InvalidRegistryObjectException e) {/* already invalid */} // NOPMD notifyWatchers(); watchers.clear(); synchronized (this) { instance = null; state = INVALID; } return true; /* clean-up parent list */ } private void notifyWatchers() { for (final Export<?> export : watchers) { try { export.unput(); } catch (final RuntimeException re) { LOGGER.log(WARNING, "Exception in service watcher", re); } } } @Override public boolean equals(final Object rhs) { if (rhs instanceof ExtensionImport) { // allocated id is a unique identifier return id == ((ExtensionImport) rhs).id; } return false; } @Override public int hashCode() { return (int) (id ^ id >>> 32); } public int compareTo(final ExtensionImport rhs) { if (id == rhs.id) { return 0; } // prefer lower allocated id return id < rhs.id ? -1 : 1; } private static Map<String, Object> collectAttributes(final IConfigurationElement config) { final Map<String, Object> map = new HashMap<String, Object>(); try { final IExtension extension = config.getDeclaringExtension(); // use @ to avoid conflicting with attributes in the XML safePut(map, "@id", extension.getUniqueIdentifier()); safePut(map, "@label", extension.getLabel()); safePut(map, "@contributor", config.getContributor()); safePut(map, "@namespace", config.getNamespaceIdentifier()); safePut(map, "@point", extension.getExtensionPointUniqueIdentifier()); // similarly use () to avoid conflicts safePut(map, "name()", config.getName()); safePut(map, "text()", config.getValue()); // now load the actual attributes from the XML for (final String key : config.getAttributeNames()) { safePut(map, key, config.getAttribute(key)); } } catch (final InvalidRegistryObjectException re) { map.clear(); // invalid, so wipe slate clean } return unmodifiableMap(map); } private static <T> void safePut(final Map<String, T> map, final String key, final T value) { if (null != value) { map.put(key, value); } } }