/*******************************************************************************
* 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.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.eclipse.gef.common.adapt.AdapterKey;
import org.eclipse.gef.common.adapt.IAdaptable;
import org.eclipse.gef.common.adapt.inject.AdapterInjectionSupport.LoggingMode;
import org.eclipse.gef.common.adapt.inject.AdapterMap.BoundAdapter;
import org.eclipse.gef.common.reflect.Types;
import com.google.common.reflect.TypeToken;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.multibindings.MapBinderBinding;
import com.google.inject.multibindings.MultibinderBinding;
import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.ConstructorBinding;
import com.google.inject.spi.ConvertedConstantBinding;
import com.google.inject.spi.ExposedBinding;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.LinkedKeyBinding;
import com.google.inject.spi.ProviderBinding;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderKeyBinding;
import com.google.inject.spi.UntargettedBinding;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
/**
* A specific {@link MembersInjector} that supports injection of adapters into
* an {@link IAdaptable} implementation class'
* {@link IAdaptable#setAdapter(TypeToken, Object, String)} method, that is
* marked as being eligible for adapter injection (see {@link InjectAdapters}).
* <p>
* Being registered for a specific {@link IAdaptable} an {@link AdapterInjector}
* will inject all instances of that type or any sub-type, evaluating all
* {@link AdapterMap} bindings that can be obtained from the {@link Injector},
* which was forwarded to it via {@link #setInjector(Injector)}. It will inject
* all adapters, for which adapter (map) bindings with a matching
* {@link AdapterMap} annotation exist. Here, matching means, that the type
* provided in the {@link AdapterMap} annotation of the {@link IAdaptable}#s
* method ( {@link AdapterMap#adaptableType()}) is either the same or a sub-type
* of the type used with the {@link AdapterMap} annotation of the related
* binding.
* <p>
* The {@link AdapterInjector} supports that type information about the actual
* adapter type may be omitted from the adapter map binding (i.e. the used
* {@link AdapterKey} only provides a role but no type key). It will try to
* infer the actual adapter type from respective bindings, or fall back to the
* type inferred from the adapter instance (which will not be adequate for
* generic types because of type erasure) in such a case.
*
* @see AdapterMap
* @see AdaptableTypeListener
* @author anyssen
*/
public class AdapterInjector implements MembersInjector<IAdaptable> {
private BindingTargetVisitor<Object, TypeToken<?>> ADAPTER_TYPE_INFERRER = new BindingTargetVisitor<Object, TypeToken<?>>() {
@Override
public TypeToken<?> visit(
ConstructorBinding<? extends Object> binding) {
return TypeToken.of(binding.getKey().getTypeLiteral().getType());
}
@Override
public TypeToken<?> visit(
ConvertedConstantBinding<? extends Object> binding) {
return null;
}
@Override
public TypeToken<?> visit(ExposedBinding<? extends Object> binding) {
return null;
}
@Override
public TypeToken<?> visit(InstanceBinding<? extends Object> binding) {
return null;
}
@Override
public TypeToken<?> visit(LinkedKeyBinding<? extends Object> binding) {
Binding<?> linkedKeyBinding = injector
.getBinding(binding.getLinkedKey());
return linkedKeyBinding.acceptTargetVisitor(this);
}
@Override
public TypeToken<?> visit(ProviderBinding<? extends Object> binding) {
return null;
}
@Override
public TypeToken<?> visit(
ProviderInstanceBinding<? extends Object> binding) {
return null;
}
@Override
public TypeToken<?> visit(
ProviderKeyBinding<? extends Object> binding) {
return null;
}
@Override
public TypeToken<?> visit(
UntargettedBinding<? extends Object> binding) {
return null;
}
};
// XXX: The MapBinderBindings of relevance are wrapped into
// ProviderInstanceBindings, so they an instance check is not sufficient
// to retrieve them, but a MultibindingsTargetVisitor is to be used.
private MultibindingsTargetVisitor<Object, MapBinderBinding<?>> ADAPTER_MAP_BINDING_FILTER = new MultibindingsTargetVisitor<Object, MapBinderBinding<?>>() {
@Override
public MapBinderBinding<?> visit(
ConstructorBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
ConvertedConstantBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
ExposedBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
InstanceBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
LinkedKeyBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
MapBinderBinding<? extends Object> mapbinding) {
return mapbinding;
}
@Override
public MapBinderBinding<?> visit(
MultibinderBinding<? extends Object> multibinding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
ProviderBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
ProviderInstanceBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
ProviderKeyBinding<? extends Object> binding) {
return null;
}
@Override
public MapBinderBinding<?> visit(
UntargettedBinding<? extends Object> binding) {
return null;
}
};
private final List<IAdaptable> deferredInstances = new ArrayList<>();
private Injector injector;
private final Method method;
private LoggingMode loggingMode;
/**
* Creates a new {@link AdapterInjector} to inject the given {@link Method},
* annotated with the given {@link AdapterMap} method annotation.
* <p>
* 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 method
* The {@link Method} to be injected.
* @param loggingMode
* The {@link LoggingMode} to use.
*/
public AdapterInjector(final Method method, LoggingMode loggingMode) {
this.method = method;
this.loggingMode = loggingMode;
}
private void deferAdapterInjection(IAdaptable adaptable,
Runnable runnable) {
if (adaptable instanceof IAdaptable.Bound) {
@SuppressWarnings("unchecked")
ReadOnlyObjectProperty<? extends IAdaptable> adaptableProperty = ((IAdaptable.Bound<? extends IAdaptable>) adaptable)
.adaptableProperty();
if (adaptableProperty.get() == null) {
// defer until we have an adaptable and can test the rest of the
// chain
adaptableProperty.addListener(new ChangeListener<IAdaptable>() {
@Override
public void changed(
ObservableValue<? extends IAdaptable> observable,
IAdaptable oldValue, IAdaptable newValue) {
if (newValue != null) {
observable.removeListener(this);
deferAdapterInjection(newValue, runnable);
}
}
});
} else {
// test rest of the chain
deferAdapterInjection(adaptableProperty.get(), runnable);
}
} else {
// the chain is complete, thus perform the injection
runnable.run();
}
}
/**
* Infers the type of the given adapter, evaluating either the related
* bindings or the runtime type of the adapter.
*
* @param adapterKey
* The key of the map binding, which is an {@link AdapterKey}.
* @param binding
* The binding related to the {@link AdapterKey}.
* @param adapter
* The adapter instance.
* @param issues
* A list of issues that might be filled with error and warning
* messages.
*
* @return A {@link TypeToken} representing the type of the given adapter
* instance.
*/
private TypeToken<?> inferAdapterType(AdapterKey<?> adapterKey,
Binding<?> binding, Object adapter, List<String> issues) {
// try to infer the actual type of the adapter from the binding
TypeToken<?> bindingInferredType = binding
.acceptTargetVisitor(ADAPTER_TYPE_INFERRER);
// perform some sanity checks
validateAdapterBinding(adapterKey, binding, adapter,
bindingInferredType, issues);
// The key type always takes precedence. Otherwise, if we could
// infer a type from the binding, we use that before falling back to
// inferring the type from the adapter instance itself.
TypeToken<?> bindingKeyType = adapterKey.getKey();
return bindingKeyType != null ? bindingKeyType
: (bindingInferredType != null ? bindingInferredType
: TypeToken.of(adapter.getClass()));
}
/**
* Performs the adapter map injection for the given adaptable instance.
*
* @param adaptable
* The adaptable to inject adapters into.
*/
protected void injectAdapters(final IAdaptable adaptable) {
// defer until the adaptable.bound chain is complete
deferAdapterInjection(adaptable, () -> {
List<String> issues = new ArrayList<>();
performAdapterInjection(adaptable, issues);
for (String issue : issues) {
if (LoggingMode.DEVELOPMENT.equals(loggingMode)
|| issue.startsWith("*** ERROR")) {
System.err.println(issue);
}
}
});
}
@Override
public void injectMembers(final IAdaptable instance) {
if (injector == null) {
// XXX: This member injector may be exercised before the injector
// (from which the map bindings are inferred) is injected. In such a
// case we need to defer the adapter injection until the injector is
// available (bug #439949).
deferredInstances.add(instance);
} else {
injectAdapters(instance);
}
}
private boolean isContextApplicable(IAdaptable injectionTarget,
BoundAdapter[] injectionContext) {
// walk up the adaptable chain and see whether context elements can be
// found
int contextIndex = 0;
String contextRole = injectionContext[contextIndex].adapterRole();
TypeToken<?> contextType = Types
.deserialize(injectionContext[contextIndex].adapterType());
IAdaptable chainElement = injectionTarget;
while (chainElement instanceof IAdaptable.Bound) {
IAdaptable nextChainElement = ((IAdaptable.Bound<?>) chainElement)
.getAdaptable();
if (nextChainElement == null) {
// this should not happen, as we defer injection
// until the chain is complete
throw new IllegalStateException(
"Adapter injection seems to have been performed while the adaptable chain is not complete yet. The adaptable is not yet set.");
}
if (nextChainElement.getAdapterKey(chainElement) == null) {
throw new IllegalStateException(
"Adapter injection seems to have been performed while the adaptable chain is not complete yet. The adapter is not yet set.");
}
if (contextRole.equals(
nextChainElement.getAdapterKey(chainElement).getRole())
&& Types.isAssignable(contextType,
TypeToken.of(chainElement.getClass()))) {
contextIndex++;
if (contextIndex == injectionContext.length) {
return true;
}
contextRole = injectionContext[contextIndex].adapterRole();
contextType = Types.deserialize(
injectionContext[contextIndex].adapterType());
}
chainElement = nextChainElement;
}
return false;
}
/**
* Performs the adapter map injection for the given adaptable instance.
*
* @param adaptable
* The adaptable to inject adapters into.
* @param issues
* The list of issues.
*/
private void performAdapterInjection(final IAdaptable adaptable,
List<String> issues) {
// XXX: We have to enter the scope before retrieving adapters
// System.out.println("Entering scope of " + adaptable);
AdaptableScopes.enter(adaptable);
// check which bindings are applicable
for (final Entry<Key<?>, Binding<?>> entry : injector.getAllBindings()
.entrySet()) {
// keep track of the applicable adapter map binding (so it can be
// used for injection later)
MapBinderBinding<?> adapterMapBinding = null;
// only consider bindings that are qualified by an AdapterMap
// binding annotation.
Key<?> key = entry.getKey();
Binding<?> binding = entry.getValue();
if ((key.getAnnotationType() != null)
&& AdapterMap.class.equals(key.getAnnotationType())) {
final AdapterMap keyAnnotation = (AdapterMap) key
.getAnnotation();
if (keyAnnotation.adaptableType()
.isAssignableFrom(adaptable.getClass())) {
if (keyAnnotation.adaptableContext().length != 0) {
// the adapter map binding is targeting a specific
// context
// if the adaptable is itself Adaptable.Bound and uses a
// role for its registration, consider that role here
if (isContextApplicable(adaptable,
keyAnnotation.adaptableContext())) {
// XXX: The MapBinderBindings of relevance are
// wrapped into
// ProviderInstanceBindings, so they an instance
// check is not
// sufficient
// to retrieve them, but a
// MultibindingsTargetVisitor is to be used.
adapterMapBinding = binding.acceptTargetVisitor(
ADAPTER_MAP_BINDING_FILTER);
}
} else {
// XXX: All adapter (map) bindings that are bound to the
// adaptable type, or to a super type or super interface
// will be considered.
// System.out.println("Applying binding for " +
// keyAnnotation.value() + " to " + type +
// " as subtype of " + methodAnnotation.value());
// XXX: The MapBinderBindings of relevance are wrapped
// into
// ProviderInstanceBindings, so they an instance check
// is not
// sufficient
// to retrieve them, but a MultibindingsTargetVisitor is
// to be used.
adapterMapBinding = binding.acceptTargetVisitor(
ADAPTER_MAP_BINDING_FILTER);
}
}
}
if (adapterMapBinding != null) {
for (final Entry<?, Binding<?>> adapterBinding : adapterMapBinding
.getEntries()) {
AdapterKey<?> adapterKey = (AdapterKey<?>) adapterBinding
.getKey();
Object adapter = adapterBinding.getValue().getProvider()
.get();
// determine adapter type
TypeToken<?> adapterType = inferAdapterType(adapterKey,
adapterBinding.getValue(), adapter, issues);
// inject the adapter
try {
// System.out.println("Inject adapter " + adapter
// + " with type " + adapterType + " for key "
// + key + " to adaptable " + adaptable);
method.setAccessible(true);
method.invoke(adaptable, new Object[] { adapterType,
adapter, adapterKey.getRole() });
} catch (final IllegalArgumentException e) {
e.printStackTrace();
} catch (final IllegalAccessException e) {
e.printStackTrace();
} catch (final InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
// System.out.println("Leaving scope of " + adaptable);
AdaptableScopes.leave(adaptable);
// System.out.println("Finished adapter injection for " + adaptable
// + " with bindings " + adapterMapBindings);
}
/**
* Sets the {@link Injector}, being used for adapter map injection.
*
* @param injector
* The {@link Injector} to use.
*/
@Inject
public void setInjector(final Injector injector) {
this.injector = injector;
// perform injections for those instances that had to be exercised
// before the injector was available (if there have been any)
for (final IAdaptable instance : deferredInstances) {
injectAdapters(instance);
}
deferredInstances.clear();
}
/**
* Validates that the given binding is not over or under specified.
*
* @param adapterKey
* The key of the map binding, which is an {@link AdapterKey}.
* @param binding
* The binding related to the {@link AdapterKey}.
* @param adapter
* The adapter instance.
* @param bindingInferredType
* The type that was inferred for the adapter instance.
* @param issues
* A list of issues that might be filled with error and warning
* messages.
*/
private void validateAdapterBinding(AdapterKey<?> adapterKey,
Binding<?> binding, Object adapter,
TypeToken<?> bindingInferredType, List<String> issues) {
TypeToken<?> bindingKeyType = adapterKey.getKey();
if (bindingInferredType != null) {
if (bindingKeyType != null) {
if (bindingKeyType.equals(bindingInferredType)) {
// a key type is given and equals the inferred type;
// issue a warning because of the superfluous
// information
issues.add("*** INFO: The actual type of adapter " + adapter
+ " could already be inferred as "
+ bindingInferredType + " from the binding at "
+ binding.getSource() + ".\n"
+ " The redundant type key "
+ bindingKeyType
+ " may be omitted in the adapter key of the binding, using "
+ (AdapterKey.DEFAULT_ROLE
.equals(adapterKey.getRole())
? "AdapterKey.defaultRole()"
: " AdapterKey.role("
+ adapterKey.getRole()
+ ")")
+ " instead.");
} else {
if (bindingInferredType
.getType() instanceof ParameterizedType) {
// we know (from a binding) that the actual type
// is a parameterized type and the key type
// is not equal, so this is a problem
issues.add("*** WARNING: The given key type "
+ bindingKeyType
+ " does not seem to match the actual type of adapter "
+ adapter + " which was inferred as "
+ bindingInferredType + " from the binding at "
+ binding.getSource() + ".\n"
+ " The adapter will only be retrievable via key types assignable to "
+ bindingKeyType
+ ". You should probably adjust your binding.");
} else {
// the actual type (inferred from the
// binding) is a raw type; the key raw type
// should at least match this raw type
if (!bindingInferredType.getRawType()
.equals(bindingKeyType.getRawType())) {
issues.add("*** ERROR: The given key (raw) type "
+ bindingKeyType.getRawType().getName()
+ " does not match the actual (raw) type of adapter "
+ adapter + " which was inferred as "
+ bindingInferredType
+ " from the binding at "
+ binding.getSource() + ".\n"
+ " The adapter will only be retrievable via key types assignable to "
+ bindingKeyType
+ ". You need to adjust your binding.");
}
}
}
}
} else {
// no type could be inferred from the binding
if (bindingKeyType == null) {
issues.add("*** WARNING: The actual type of adapter " + adapter
+ " could not be inferred from the binding at "
+ binding.getSource()
+ ". The adapter will only be retrievable via key types assignable to "
+ TypeToken.of(adapter.getClass())
+ ", which is the actual type inferred from the instance.\n"
+ " You should probably adjust your binding to provide a type key using "
+ (AdapterKey.DEFAULT_ROLE.equals(adapterKey.getRole())
? "AdapterKey.get(<type>)"
: "AdapterKey.get(<type>, "
+ adapterKey.getRole() + ")")
+ ".");
} else {
// check that at least key raw type and the type inferred
// from the adapter instance match
if (!bindingKeyType.getRawType()
.isAssignableFrom(adapter.getClass())
|| (!adapter.getClass().isAnonymousClass()
&& !adapter.getClass().isAssignableFrom(
bindingKeyType.getRawType()))) {
issues.add("*** ERROR: The given key (raw) type "
+ bindingKeyType.getRawType().getName()
+ " does not match the actual (raw) type of adapter "
+ adapter + ", which was inferred as "
+ adapter.getClass().getName() + ".\n"
+ " You need to adjust your binding.");
} else {
// warn that the type could not be inferred and thus
// both types have to match
issues.add("*** WARNING: The actual type of adapter "
+ adapter
+ " could not be inferred from the binding at "
+ binding.getSource()
+ ". Therefore, the given type key "
+ bindingKeyType + " can not be confirmed.\n"
+ " Make sure the provided type key "
+ bindingKeyType
+ " matches to the actual type of the adapter.");
}
}
}
}
}