/*******************************************************************************
* Copyright (c) 2014, 2016 itemis AG and others.
* 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:
* Alexander Nyßen (itemis AG) - initial API and implementation
* Matthias Wienand (itemis AG) - contributions for Bugzilla #496777
*
*******************************************************************************/
package org.eclipse.gef.common.adapt.inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.gef.common.adapt.IAdaptable;
import org.eclipse.gef.common.adapt.inject.AdapterInjectionSupport.LoggingMode;
import com.google.common.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.MembersInjector;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
/**
* A specific {@link TypeListener} to support adapter injection. It will
* register an {@link AdapterInjector}, which will perform the adapter
* injection, for each {@link IAdaptable} that is eligible (see
* {@link InjectAdapters}.
* <p>
* In order to function properly, an {@link AdaptableTypeListener} has to be
* bound in a Guice {@link Module} as follows:
*
* <pre>
* AdaptableTypeListener adaptableTypeListener = new AdaptableTypeListener();
* requestInjection(adaptableTypeListener);
* bindListener(Matchers.any(), adaptableTypeListener);
* </pre>
*
* The call to <code>requestInjection()</code> is important to ensure that
* {@link AdaptableTypeListener#setInjector(Injector)} will get injected.
* Without it, the {@link AdaptableTypeListener} will not function properly.
* <p>
* Clients should not register an {@link AdaptableTypeListener} themselves but
* rather install {@link AdapterInjectionSupport} in one of the {@link Module}s
* that are used by the {@link Injector}.
*
* @see AdapterInjectionSupport
*
* @author anyssen
*
*/
public class AdaptableTypeListener implements TypeListener {
// the injector used to obtain adapter map bindings
private Injector injector;
private LoggingMode loggingMode;
// used to keep track of members that are to be injected before we have
// obtained the injector (bug #439949)
private Set<AdapterInjector> nonInjectedMemberInjectors = new HashSet<>();
/**
* Constructs a new {@link AdaptableTypeListener} and specifies the
* {@link LoggingMode} to use. If in {@link LoggingMode#DEVELOPMENT} mode,
* binding-related information, warning, and error messages will be printed.
* If in {@link LoggingMode#PRODUCTION} mode, only error messages will be
* printed, and information and warning messages will be suppressed.
*
* @param loggingMode
* The {@link LoggingMode} to use.
*/
public AdaptableTypeListener(LoggingMode loggingMode) {
this.loggingMode = loggingMode;
}
/**
* Checks that the given method complies to the signature of
* {@link IAdaptable#setAdapter(TypeToken, Object, String)}.
*
* @param method
* The {@link Method} to test.
* @return <code>true</code> if the method has a compatible signature,
* <code>false</code> otherwise.
*/
protected boolean eligibleForAdapterInjection(final Method method) {
// method has to be annotated with @InjectAdapters
if (method.getAnnotation(InjectAdapters.class) != null) {
// signature has to comply with IAdaptable#setAdapter(TypeToken,
// Object, String).
return method.getName().equals("setAdapter")
&& method.getParameterTypes().length == 3
&& method.getParameterTypes()[0].equals(TypeToken.class)
&& method.getParameterTypes()[1].equals(Object.class)
&& method.getParameterTypes()[2].equals(String.class);
}
return false;
}
@SuppressWarnings("unchecked")
private <T extends Annotation> T getAnnotation(Annotation[] annotations,
Class<T> annotationType) {
for (Annotation a : annotations) {
if (annotationType.isAssignableFrom(a.annotationType())) {
return (T) a;
}
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
if (IAdaptable.class.isAssignableFrom(type.getRawType())) {
// TODO: method check should be moved into members injector,
// here, only the type + an additional operation should be checked.
for (final Method method : type.getRawType().getMethods()) {
// check that AdapterMap annotation is not used to mark
// injection points (but only in bindings).
for (int i = 0; i < method
.getParameterAnnotations().length; i++) {
AdapterMap adapterMapAnnotation = getAnnotation(
method.getParameterAnnotations()[i],
AdapterMap.class);
if (adapterMapAnnotation != null) {
encounter.addError(
"@AdapterMap annotation may only be used in adapter map bindings, not to mark an injection point. Annotate method with @InjectAdapters instead.",
method);
}
}
// we have a method annotated with AdapterBinding
if (eligibleForAdapterInjection(method)) {
// check that no Guice @Inject annotation is present on the
// method (so no interference occurs).
Inject injectAnnotation = method
.getAnnotation(Inject.class);
if (injectAnnotation != null) {
encounter.addError(
"To prevent that Guice member injection interferes with adapter injection, no @Inject annotation may be used on a method that provides an @InjectAdapters annotation.");
}
// register member injector on the IAdaptable (and provide
// the method to it, so it does not have to look it up
// again).
AdapterInjector membersInjector = new AdapterInjector(
method, loggingMode);
if (injector != null) {
injector.injectMembers(membersInjector);
} else {
nonInjectedMemberInjectors.add(membersInjector);
}
// System.out.println("Registering member injector to "
// + type);
encounter.register((MembersInjector<I>) membersInjector);
}
}
}
}
/**
* In order to work, the {@link AdaptableTypeListener} needs to obtain a
* reference to an {@link Injector}, which is forwarded to the
* {@link AdapterInjector}, which it registers for any {@link IAdaptable}
* encounters, to obtain the {@link AdapterMap} bindings to be injected.
*
* @param injector
* The injector that is forwarded (used to inject) the
* {@link AdapterInjector}.
*/
@Inject
public void setInjector(Injector injector) {
this.injector = injector;
for (AdapterInjector memberInjector : nonInjectedMemberInjectors) {
injector.injectMembers(memberInjector);
}
nonInjectedMemberInjectors.clear();
}
}