/** * Copyright (c) 2011 Sebastian Proksch. * 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: * Sebastian Proksch - initial API and implementation */ package org.eclipse.recommenders.internal.apidocs.rcp; import static com.google.common.base.Optional.*; import static com.google.common.collect.Sets.newLinkedHashSet; import static org.eclipse.recommenders.utils.Checks.ensureIsNotNull; import static org.eclipse.recommenders.utils.Pair.newPair; import static org.eclipse.recommenders.utils.Throws.throwIllegalArgumentException; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import javax.inject.Inject; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.recommenders.apidocs.rcp.ApidocProvider; import org.eclipse.recommenders.apidocs.rcp.JavaSelectionSubscriber; import org.eclipse.recommenders.rcp.JavaElementSelectionEvent; import org.eclipse.recommenders.rcp.JavaElementSelectionEvent.JavaElementSelectionLocation; import org.eclipse.recommenders.utils.Pair; import org.eclipse.swt.widgets.Composite; import com.google.common.base.Optional; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; public class SubscriptionManager { private final Multimap<Subscription, Pair<ApidocProvider, Method>> subscriptions = LinkedHashMultimap.create(); @Inject public SubscriptionManager(final List<ApidocProvider> providers) { ensureIsNotNull(providers); for (final ApidocProvider p : providers) { register(p); } } private void register(final ApidocProvider provider) { final Set<Pair<Method, JavaSelectionSubscriber>> annotatedMethods = findAnnotatedMethods(provider); if (annotatedMethods.isEmpty()) { throwIllegalArgumentException("no listeners found"); //$NON-NLS-1$ } for (final Pair<Method, JavaSelectionSubscriber> t : annotatedMethods) { addSubscription(provider, t.getFirst(), t.getSecond()); } } private Set<Pair<Method, JavaSelectionSubscriber>> findAnnotatedMethods(final ApidocProvider provider) { final Set<Pair<Method, JavaSelectionSubscriber>> methods = newLinkedHashSet(); final Class<?> clazz = provider.getClass(); for (final Method m : clazz.getMethods()) { final JavaSelectionSubscriber annotation = m.getAnnotation(JavaSelectionSubscriber.class); if (annotation != null) { ensureCorrectMethodSignature(m); methods.add(newPair(m, annotation)); } } return methods; } private static void ensureCorrectMethodSignature(final Method m) { final Class<?>[] params = m.getParameterTypes(); ensureParameterLengthIsThree(params, m); ensureFirstParameterTypeIsJavaElement(params, m); ensureSecondParameterTypeIsJavaSelectionEvent(params, m); ensureThirdParameterTypeIsComposite(params, m); } private static void ensureParameterLengthIsThree(final Class<?>[] params, final Method m) { if (params.length != 3) { throwIllegalArgumentException("error in %s: at least 3 parameters expected", m.toGenericString()); //$NON-NLS-1$ } } private static void ensureFirstParameterTypeIsJavaElement(final Class<?>[] params, final Method m) { if (!IJavaElement.class.isAssignableFrom(params[0])) { throwIllegalArgumentException("error in %s: first parameter needs to be %s or a subclass", //$NON-NLS-1$ m.toGenericString(), IJavaElement.class.getName()); } } private static void ensureSecondParameterTypeIsJavaSelectionEvent(final Class<?>[] params, final Method m) { if (!JavaElementSelectionEvent.class.isAssignableFrom(params[1])) { throwIllegalArgumentException("error in %s: second parameter needs to be %s or a subclass", //$NON-NLS-1$ m.toGenericString(), JavaElementSelectionEvent.class.getName()); } } private static void ensureThirdParameterTypeIsComposite(final Class<?>[] params, final Method m) { if (!Composite.class.isAssignableFrom(params[2])) { throwIllegalArgumentException("error in %s: third parameter needs to be %s or a subclass", //$NON-NLS-1$ m.toGenericString(), Composite.class.getName()); } } private void addSubscription(final ApidocProvider provider, final Method method, final JavaSelectionSubscriber annotation) { final JavaElementSelectionLocation[] locs = annotation.value(); final Class<?> javaElementType = method.getParameterTypes()[0]; final Pair<ApidocProvider, Method> subscriber = newPair(provider, method); if (locs.length == 0) { final Subscription subscription = Subscription.create(javaElementType, null); subscriptions.put(subscription, subscriber); } else { for (final JavaElementSelectionLocation loc : locs) { final Subscription subscription = Subscription.create(javaElementType, loc); subscriptions.put(subscription, subscriber); } } } /** * Returns a method of the given provider that is subscribed for the selection event - or <em>absent</em> if none is * found. If the subscription of multiple methods overlaps, no guarantee is given which method is returned */ public Optional<Method> findSubscribedMethod(final ApidocProvider provider, final JavaElementSelectionEvent selection) { for (final Subscription s : subscriptions.keySet()) { if (s.isInterestedIn(selection)) { for (final Pair<ApidocProvider, Method> t : subscriptions.get(s)) { if (provider.equals(t.getFirst())) { return of(t.getSecond()); } } } } return absent(); } public static class Subscription { private Class<?> interestedJavaElementClass; private JavaElementSelectionLocation interestedLocation; public static Subscription create(final Class<?> clazz, final JavaElementSelectionLocation loc) { final Subscription subscription = new Subscription(); subscription.interestedJavaElementClass = clazz; subscription.interestedLocation = loc; return subscription; } public boolean isInterestedIn(final JavaElementSelectionEvent selection) { return isInterestedIn(selection.getElement()) && isInterestedIn(selection.getLocation()); } private boolean isInterestedIn(final IJavaElement element) { return interestedJavaElementClass.isAssignableFrom(element.getClass()); } private boolean isInterestedIn(final JavaElementSelectionLocation firedLoc) { return matchesAllLocations() || matchesFiredLocation(firedLoc); } private boolean matchesFiredLocation(final JavaElementSelectionLocation firedLoc) { return interestedLocation.equals(firedLoc); } private boolean matchesAllLocations() { return interestedLocation == null; } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } @Override public boolean equals(final Object obj) { return EqualsBuilder.reflectionEquals(this, obj); } } }