/******************************************************************************* * Copyright (c) 2015, 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 * *******************************************************************************/ package org.eclipse.gef.common.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.gef.common.adapt.AdaptableSupport; import org.eclipse.gef.common.adapt.AdapterKey; import org.eclipse.gef.common.adapt.AdapterStore; import org.eclipse.gef.common.adapt.IAdaptable; import org.eclipse.gef.common.adapt.inject.AdapterInjectionSupport; import org.eclipse.gef.common.adapt.inject.AdapterInjectionSupport.LoggingMode; import org.eclipse.gef.common.adapt.inject.AdapterInjector; import org.eclipse.gef.common.adapt.inject.AdapterMaps; import org.eclipse.gef.common.adapt.inject.InjectAdapters; import org.junit.Test; import com.google.common.reflect.TypeToken; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.multibindings.MapBinder; import javafx.beans.property.ReadOnlyMapProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.ObservableMap; public class AdapterInjectorTests { private static class AdapterStoreBoundAdaptable implements IAdaptable, IAdaptable.Bound<AdapterStore> { private AdaptableSupport<AdapterStoreBoundAdaptable> ads = new AdaptableSupport<>( this); private ReadOnlyObjectWrapper<AdapterStore> adaptableProperty = new ReadOnlyObjectWrapper<>(); @Override public ReadOnlyObjectProperty<AdapterStore> adaptableProperty() { return adaptableProperty.getReadOnlyProperty(); } @Override public ReadOnlyMapProperty<AdapterKey<?>, Object> adaptersProperty() { return ads.adaptersProperty(); } @Override public AdapterStore getAdaptable() { return adaptableProperty.get(); } @Override public <T> T getAdapter(AdapterKey<T> key) { return ads.getAdapter(key); } @Override public <T> T getAdapter(Class<T> key) { return ads.getAdapter(key); } @Override public <T> T getAdapter(TypeToken<T> key) { return ads.getAdapter(key); } @Override public <T> AdapterKey<T> getAdapterKey(T adapter) { return ads.getAdapterKey(adapter); } @Override public ObservableMap<AdapterKey<?>, Object> getAdapters() { return ads.getAdapters(); } @Override public <T> Map<AdapterKey<? extends T>, T> getAdapters( Class<? super T> key) { return ads.getAdapters(key); } @Override public <T> Map<AdapterKey<? extends T>, T> getAdapters( TypeToken<? super T> key) { return ads.getAdapters(key); } @Override public void setAdaptable(AdapterStore adaptable) { adaptableProperty.set(adaptable); } @Override public <T> void setAdapter(T adapter) { ads.setAdapter(adapter); } @Override public <T> void setAdapter(T adapter, String role) { ads.setAdapter(adapter, role); } @Override public <T> void setAdapter(TypeToken<T> adapterType, T adapter) { ads.setAdapter(adapterType, adapter); } @InjectAdapters @Override public <T> void setAdapter(TypeToken<T> adapterType, T adapter, String role) { ads.setAdapter(adapterType, adapter, role); } @Override public <T> void unsetAdapter(T adapter) { ads.unsetAdapter(adapter); } } private final class AdapterStoreExtension extends AdapterStore { @InjectAdapters @Override public <T> void setAdapter(TypeToken<T> adapterType, T adapter, String role) { try { super.setAdapter(adapterType, adapter, role); } catch (Exception e) { // the injection will fail; we capture this silently here, // as it is not relevant for the test } } } private static class ParameterizedSubType<T> extends RawType { } private static class RawType { } /** * Tests that a warning message is given when no type key is provided in the * binding, and the actual type could also not be inferred from the binding. */ @Test public void ensureMissingKeyDetected() throws Exception { Module module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // use raw type as key and target adapterMapBinder.addBinding(AdapterKey.defaultRole()) .toInstance(new ParameterizedSubType<Integer>()); } }; AdapterStore adaptable = new AdapterStoreExtension(); List<String> issues = performInjection(adaptable, module); assertEquals(1, issues.size()); // System.out.println(issues.get(0)); assertTrue(issues.get(0).contains("WARNING")); assertTrue(issues.get(0).contains( "The actual type of adapter org.eclipse.gef.common.tests.AdapterInjectorTests$ParameterizedSubType")); assertTrue(issues.get(0) .contains("could not be inferred from the binding at")); } /** * Tests that a warning message is given when the actual type of an adapter * could be precisely inferred from the binding, and the binding provides a * type key as well. */ @Test public void ensureSuperfluousKeyDetected() throws Exception { Module module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // use raw type as key and target adapterMapBinder.addBinding(AdapterKey.get(RawType.class)) .to(RawType.class); } }; AdapterStore adaptable = new AdapterStore(); List<String> issues = performInjection(adaptable, module); assertEquals(1, issues.size()); // System.out.println(issues.get(0)); assertTrue(issues.get(0).contains("INFO")); assertTrue(issues.get(0).contains( "The redundant type key org.eclipse.gef.common.tests.AdapterInjectorTests$RawType may be omitted in the adapter key of the binding, using AdapterKey.defaultRole() instead.")); } /** * Tests that a warning message is given when no type could be inferred from * the binding, and the type key can thus not be validated. */ @Test public void ensureUncertainKeyDetected() throws Exception { Module module = new AbstractModule() { @SuppressWarnings("serial") @Override protected void configure() { install(new AdapterInjectionSupport()); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // use raw type as key and target adapterMapBinder.addBinding(AdapterKey .get(new TypeToken<ParameterizedSubType<Integer>>() { })).toInstance(new ParameterizedSubType<Integer>()); } }; AdapterStore adaptable = new AdapterStore(); List<String> issues = performInjection(adaptable, module); assertEquals(1, issues.size()); // System.out.println(issues.get(0)); assertTrue(issues.get(0).contains("WARNING")); assertTrue(issues.get(0).contains( "The actual type of adapter org.eclipse.gef.common.tests.AdapterInjectorTests$ParameterizedSubType")); assertTrue(issues.get(0) .contains("could not be inferred from the binding at")); assertTrue(issues.get(0).contains("Make sure the provided type key ")); assertTrue(issues.get(0) .contains("matches to the actual type of the adapter.")); } /** * Tests that a warning message is given when a type different to the key * type could be inferred from the binding. */ @Test public void ensureUnderspecifiedKeyDetected() throws Exception { Module module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // use raw type as key and target adapterMapBinder.addBinding(AdapterKey.get(RawType.class)) .to(new TypeLiteral<ParameterizedSubType<Object>>() { }); } }; AdapterStore adaptable = new AdapterStoreExtension(); List<String> issues = performInjection(adaptable, module); assertEquals(1, issues.size()); // System.out.println(issues.get(0)); assertTrue(issues.get(0).contains( "The given key type org.eclipse.gef.common.tests.AdapterInjectorTests$RawType does not seem to match the actual type of adapter org.eclipse.gef.common.tests.AdapterInjectorTests$ParameterizedSubType")); } /** * Tests that an error message is given when the binding provides a type * key, whose raw type does not the actual (raw) type of the adapter, as it * was inferred from the binding (if possible) or the adapter instance. */ @Test public void ensureUnmatchedRawKeyDetected() throws Exception { // First case: a module that allows to infer the actual type from the // binding Module module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // use raw type as key and target adapterMapBinder.addBinding(AdapterKey.get(RawType.class)) .to(ParameterizedSubType.class); } }; AdapterStore adaptable = new AdapterStoreExtension(); List<String> issues = performInjection(adaptable, module); assertEquals(1, issues.size()); // System.out.println(issues.get(0)); assertTrue(issues.get(0).contains("ERROR")); assertTrue(issues.get(0).contains( "The given key (raw) type org.eclipse.gef.common.tests.AdapterInjectorTests$RawType does not match the actual (raw) type of adapter org.eclipse.gef.common.tests.AdapterInjectorTests$ParameterizedSubType")); // Second case: a module that does not allow to infer the actual type // from the // binding module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // use raw type as key and target adapterMapBinder.addBinding(AdapterKey.get(RawType.class)) .toInstance(new ParameterizedSubType<Integer>()); } }; adaptable = new AdapterStoreExtension(); issues = performInjection(adaptable, module); assertEquals(1, issues.size()); // System.out.println(issues.get(0)); assertTrue(issues.get(0).contains("ERROR")); assertTrue(issues.get(0).contains( "The given key (raw) type org.eclipse.gef.common.tests.AdapterInjectorTests$RawType does not match the actual (raw) type of adapter org.eclipse.gef.common.tests.AdapterInjectorTests$ParameterizedSubType")); } @SuppressWarnings("serial") @Test public void injectAdapters() { Module module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // constructor binding adapterMapBinder.addBinding(AdapterKey .get(new TypeToken<ParameterizedSubType<Integer>>() { }, "a1")) .to(new TypeLiteral<ParameterizedSubType<Integer>>() { }); // instance binding adapterMapBinder.addBinding( AdapterKey.get(new TypeToken<Provider<Integer>>() { }, "a2")).toInstance(new Provider<Integer>() { @Override public Integer get() { return 5; } }); // provider binding adapterMapBinder.addBinding( AdapterKey.get(new TypeToken<Provider<Integer>>() { }, "a3")).toProvider(new Provider<Provider<Integer>>() { @Override public Provider<Integer> get() { return new Provider<Integer>() { @Override public Integer get() { return 5; } }; } }); } }; Injector injector = Guice.createInjector(module); AdapterStore adapterStore = new AdapterStore(); injector.injectMembers(adapterStore); assertNotNull(adapterStore.getAdapter( AdapterKey.get(new TypeToken<ParameterizedSubType<Integer>>() { }, "a1"))); // retrieve a parameterized type bound as instance assertNotNull(adapterStore .getAdapter(AdapterKey.get(new TypeToken<Provider<Integer>>() { }, "a2"))); assertNotNull(adapterStore .getAdapter(AdapterKey.get(new TypeToken<Provider<Integer>>() { }, "a3"))); } /** * Tests that adapters, which are bound to an adaptable of a certain role * are injected to an adaptable, that is itself bound as an adapter with the * respective role. */ @SuppressWarnings("serial") @Test public void injectAdaptersToBoundAdaptableOfRole() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final String firstRole = "firstRole"; final String secondRole = "secondRole"; final String role1 = "a1"; final String role2 = "a2"; final String role3 = "a3"; Module module = new AbstractModule() { @Override protected void configure() { install(new AdapterInjectionSupport()); MapBinder<AdapterKey<?>, Object> adapterMapBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStore.class); // register adapter for the first role adapterMapBinder.addBinding(AdapterKey.role(firstRole)) .to(AdapterStoreBoundAdaptable.class); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> firstRoleBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStoreBoundAdaptable.class, AdapterKey.get(AdapterStoreBoundAdaptable.class, firstRole)); // register adapter firstRoleBinder.addBinding(AdapterKey.role(role1)) .to(RawType.class); firstRoleBinder.addBinding(AdapterKey .get(new TypeToken<ParameterizedSubType<Integer>>() { }, role2)) .to(new TypeLiteral<ParameterizedSubType<Integer>>() { }); firstRoleBinder.addBinding( AdapterKey.get(new TypeToken<Provider<Integer>>() { }, role3)).toInstance(new Provider<Integer>() { @Override public Integer get() { return 5; } }); // register adapter for the second role adapterMapBinder.addBinding(AdapterKey.role(secondRole)) .to(AdapterStoreBoundAdaptable.class); // create map bindings for AdapterStore, which is an IAdaptable MapBinder<AdapterKey<?>, Object> secondRoleBinder = AdapterMaps .getAdapterMapBinder(binder(), AdapterStoreBoundAdaptable.class, AdapterKey.get(AdapterStoreBoundAdaptable.class, secondRole)); // register adapter secondRoleBinder.addBinding(AdapterKey.role(role1)) .to(RawType.class); secondRoleBinder.addBinding(AdapterKey .get(new TypeToken<ParameterizedSubType<Integer>>() { }, role2)) .to(new TypeLiteral<ParameterizedSubType<Integer>>() { }); secondRoleBinder.addBinding( AdapterKey.get(new TypeToken<Provider<Integer>>() { }, role3)).toInstance(new Provider<Integer>() { @Override public Integer get() { return 5; } }); } }; Injector injector = Guice.createInjector(module); AdapterStore adapterStore = new AdapterStore(); injector.injectMembers(adapterStore); // test first role AdapterStoreBoundAdaptable adaptableBound = adapterStore.getAdapter( AdapterKey.get(AdapterStoreBoundAdaptable.class, firstRole)); assertNotNull(adaptableBound); assertNotNull(adaptableBound .getAdapter(AdapterKey.get(RawType.class, role1))); // retrieve by raw type (which works even if we could not infer a type // from the binding) assertNotNull(adaptableBound .getAdapter(AdapterKey.get(ParameterizedSubType.class, role2))); // retrieve by parameterized type token (which only works if we could // infer a type from the binding) assertNotNull(adaptableBound.getAdapter( AdapterKey.get(new TypeToken<ParameterizedSubType<Integer>>() { }, role2))); // retrieve a parameterized type bound as instance assertNotNull(adaptableBound .getAdapter(AdapterKey.get(new TypeToken<Provider<Integer>>() { }, role3))); // test second role adaptableBound = adapterStore.getAdapter( AdapterKey.get(AdapterStoreBoundAdaptable.class, secondRole)); assertNotNull(adaptableBound); assertNotNull(adaptableBound .getAdapter(AdapterKey.get(RawType.class, role1))); // retrieve by raw type (which works even if we could not infer a type // from the binding) assertNotNull(adaptableBound .getAdapter(AdapterKey.get(ParameterizedSubType.class, role2))); // retrieve by parameterized type token (which only works if we could // infer a type from the binding) assertNotNull(adaptableBound.getAdapter( AdapterKey.get(new TypeToken<ParameterizedSubType<Integer>>() { }, role2))); // retrieve a parameterized type bound as instance assertNotNull(adaptableBound .getAdapter(AdapterKey.get(new TypeToken<Provider<Integer>>() { }, role3))); } protected List<String> performInjection(AdapterStore adaptable, Module module) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Injector injector = Guice.createInjector(module); AdapterInjector adapterInjector = new AdapterInjector( AdapterStore.class.getMethod("setAdapter", TypeToken.class, Object.class, String.class), LoggingMode.DEVELOPMENT); adapterInjector.setInjector(injector); List<String> issues = new ArrayList<>(); // call adapterInjector.injectAdapters(adaptable, issues); Method injectAdaptersMethod = AdapterInjector.class.getDeclaredMethod( "performAdapterInjection", IAdaptable.class, List.class); injectAdaptersMethod.setAccessible(true); injectAdaptersMethod.invoke(adapterInjector, adaptable, issues); return issues; } }