/*******************************************************************************
* 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);
}
}
}