package sk.stuba.fiit.perconik.core.plugin; import java.util.List; import java.util.Set; import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import sk.stuba.fiit.perconik.eclipse.core.runtime.ExtendedPlugin; import sk.stuba.fiit.perconik.eclipse.core.runtime.PluginConsole; import sk.stuba.fiit.perconik.environment.Environment; import static java.util.Arrays.asList; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Lists.newArrayListWithCapacity; abstract class AbstractExtensionProcessor<T> { private final ExtendedPlugin plugin; private final String point; private final Set<Class<?>> types; private final SetMultimap<Class<?>, String> contributors; private final ListMultimap<Class<?>, Object> extensions; AbstractExtensionProcessor(final String point, final Set<Class<?>> types) { checkArgument(!isNullOrEmpty(point)); this.plugin = Activator.defaultInstance(); this.point = point; this.types = ImmutableSet.copyOf(types); this.contributors = LinkedHashMultimap.create(this.types.size(), 4); this.extensions = LinkedListMultimap.create(this.types.size()); } final PluginConsole console() { return this.plugin.getConsole(); } final T process() { return this.processRegistry(Platform.getExtensionRegistry()); } private final T processRegistry(final IExtensionRegistry registry) { IConfigurationElement[] elements = registry.getConfigurationElementsFor(this.point); for (IConfigurationElement element: elements) { try { String contributor = element.getContributor().getName(); if (!Environment.debug && contributor.endsWith("debug")) { continue; } Object object = element.createExecutableExtension("class"); this.collectExecutableExtensions(element.getContributor().getName(), object); element.getContributor(); } catch (CoreException failure) { this.console().error("Creating executable extension " + element.getName() + " failed for value " + element.getValue(), failure); } } return this.processExtensions(); } abstract T processExtensions(); private final void collectExecutableExtensions(final String contributor, final Object object) { int matches = 0; for (Class<?> type: this.types) { if (type.isInstance(object)) { this.contributors.put(type, contributor); this.extensions.put(type, type.cast(object)); matches ++; } } if (matches == 0) { this.handleUnknownExecutableExtension(object); } } private final void handleUnknownExecutableExtension(final Object object) { UnsupportedOperationException failure = new UnsupportedOperationException(object.toString()); this.console().error("Unable to process executable extension of type " + object.getClass().getName(), failure); } abstract class SafeBlock implements ISafeRunnable { private final AbstractExtensionProcessor<T> processor; final Object object; SafeBlock(final Object object) { this.processor = AbstractExtensionProcessor.this; this.object = checkNotNull(object); } public final void execute() { SafeRunner.run(this); } public final void handleException(final Throwable exception) { this.processor.console().error("Exception in a block with object " + String.valueOf(this.object), exception); } } abstract class SafeGet<R> extends SafeBlock { final Class<R> type; private R result; SafeGet(final Object object, final Class<R> type) { super(object); this.type = checkNotNull(type); } public final void run() throws Exception { this.result = this.type.cast(this.get()); } abstract R get() throws Exception; public final R getResult() { this.execute(); return this.result; } } @SuppressWarnings("static-method") final <R> R resultOf(final SafeGet<R> operation) { return operation.getResult(); } final boolean atLeastOneSupplied(final Class<?> ... types) { return this.atLeastOneSupplied(asList(types)); } final boolean atLeastOneSupplied(final Iterable<Class<?>> types) { for (Class<?> type: types) { if (this.hasExtensions(type)) { return true; } } return false; } final boolean emptyWithNotice(final List<?> objects, final String subject) { if (objects.isEmpty()) { this.console().notice("No %s provided, using default %s", subject, this.point, subject); return true; } return false; } final boolean emptyOrNotSingletonWithWarning(final List<?> objects, final String subject) { if (objects.isEmpty()) { return true; } if (objects.size() != 1) { this.console().warning("More than one %s provided, using default %s", subject, this.point, subject); return true; } return false; } private final <E> Class<E> requireType(final Class<E> type) { checkArgument(this.types.contains(type)); return type; } final boolean hasContributors(final Class<?> type) { return this.contributors.containsKey(type); } final boolean hasExtensions(final Class<?> type) { return this.extensions.containsKey(type); } final Set<String> getContributors(final Class<?> type) { return this.contributors.get(type); } final <E> List<E> getExtensions(final Class<E> type) { List<?> extensions = this.extensions.get(this.requireType(type)); List<E> result = newArrayListWithCapacity(extensions.size()); for (Object extension: extensions) { result.add(type.cast(extension)); } return result; } }