/******************************************************************************* * 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.adapt.inject; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import org.eclipse.gef.common.adapt.IAdaptable; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.Scope; /** * A Guice {@link Scope} that is bound to an {@link IAdaptable}-compliant type * and has to be scoped to a respective instance of that type. * <p> * After the scope has been entered for an {@link IAdaptable} instance (which * automatically switches the scope to this {@link IAdaptable} instance), the * {@link AdaptableScope} will maintain a set of scoped objects for the * respective {@link IAdaptable} instance. * * The scoped provider being returned by {@link #scope(Key, Provider)} will * always (recycle) objects from this set of scoped objects, only creating new * instances if a respective instance is not already contained in the set of * scoped objects. It will always refer to the set of scoped objects bound to * the {@link IAdaptable} instance the scope was switched to last, preserving * the set of objects instances for all other {@link IAdaptable} instances. * <p> * Leaving the scope for an {@link IAdaptable} instance will clear the set of * scoped objects for this {@link IAdaptable} instance, so no scoped objects may * be recycled afterwards. The {@link AdaptableScope} will have to be re-entered * for the {@link IAdaptable} instance after it has been left for it. * * @author anyssen * * @param <A> * The type of {@link IAdaptable} this {@link Scope} is bound to. */ class AdaptableScope<A extends IAdaptable> implements Scope { // Maintain a set of scoped instances per adaptable instance // XXX: As the scoped instances need to be shared by scopes, to which the // type of the adaptable instance is applicable, we need to use a static // field here. The scope method will ensure that only entered scopes really // accesses the field. // FIXME: We need to ensure scoped instances can be garbage collected (use // week references here) private static Map<IAdaptable, Map<Key<?>, Object>> scopedInstances = new IdentityHashMap<>(); private A adaptable = null; private Class<? extends A> type; /** * Creates a new {@link AdaptableScope} for the given {@link IAdaptable} * type. * * @param type * The {@link IAdaptable} type this scope is responsible for. */ public AdaptableScope(Class<? extends A> type) { this.type = type; } /** * Enters this scope for the given {@link IAdaptable} instance and binds the * scope to it, so that the {@link Provider} returned by * {@link #scope(Key, Provider)} will return (recycled) instances from a set * of scoped objects maintained for the {@link IAdaptable} instance, until * the scope is bound to another {@link IAdaptable} instance or left (see * {@link #leave(IAdaptable)}). * * @param instance * The {@link IAdaptable} instance to enter (and bind) this * {@link AdaptableScope} for. */ public void enter(A instance) { if (!scopedInstances.containsKey(instance)) { scopedInstances.put(instance, new HashMap<Key<?>, Object>()); } // System.out.println( // "Entering " + this + " of " + type + " for " + instance + "."); this.adaptable = instance; } /** * Leaves this scope for the given {@link IAdaptable} instance, resulting in * unbinding this scope from it and clearing the set of scoped objects * maintained for it. * <p> * The scope may not be switched back to the {@link IAdaptable} instance * before having been re-entered for it (see {@link #enter(IAdaptable)}). * * @param instance * The {@link IAdaptable} instance to (unbind and) leave this * {@link AdaptableScope} for. */ public void leave(A instance) { // System.out.println( // "Leaving " + this + " of " + type + " for " + instance + "."); this.adaptable = null; } @Override public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { return new Provider<T>() { @SuppressWarnings("unchecked") @Override public T get() { if (adaptable == null) { // System.err.println("Scope " + AdaptableScope.this + " of // " // + type + " was not entered"); throw new IllegalStateException(key + " is scoped to adaptable '" + type + "', for which no scope has been activated. You can only scope adapters in a scope of a transitive adaptable."); } else { // FIXME: We need to process all scopes of superclasses and // interfaces here to retrieve an instance. // obtain the map of scoped instances for the given // adaptable Map<Key<?>, Object> scope = scopedInstances.get(adaptable); // retrieve a cached instance from the scope (if it exists) Object instance = scope.get(key); if (instance == null) { // obtain a new instance instance = unscoped.get(); // keep track of the instance (for later access) if (instance != null) { scope.put(key, instance); } } return (T) instance; } } }; } }