/*******************************************************************************
* Copyright (c) 2015, 2017 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.Map;
import java.util.Stack;
import org.eclipse.gef.common.adapt.IAdaptable;
/**
* A utility class to support adaptable-based scoping. It will recursively enter
* and leave all transitive adaptable scopes (reachable by navigating the
* adaptable chain) for a given adaptable. An internal stack is maintained, so
* only the last {@link #enter(IAdaptable) entered} scope may be
* {@link #leave(IAdaptable) left}. When leaving a scope, the previous state is
* restored, i.e. the last entered scope will be entered again.
*
* @see AdaptableScope
*
* @author anyssen
*
*/
public class AdaptableScopes {
private interface ScopeProcessor {
public void process(Class<? extends IAdaptable> adaptableType,
final IAdaptable adaptableInstance);
}
// one adaptable scope for each type of adaptable
// FIXME: Only scopes for raw runtime types should be maintained, i.e.
// scopes for interfaces and abstract base types should not be maintained.
private static final Map<Class<? extends IAdaptable>, AdaptableScope<? extends IAdaptable>> scopes = new HashMap<>();
private static final Stack<IAdaptable> history = new Stack<>();
/**
* Transitively enters the {@link AdaptableScope} of all
* {@link IAdaptable}-compliant types (i.e. (super-)classes implementing
* {@link IAdaptable} and (super-)interfaces extending {@link IAdaptable})
* of the given {@link IAdaptable} for the given {@link IAdaptable}.
*
* @param adaptable
* The {@link IAdaptable} instance, for whose types to enter the
* {@link AdaptableScope}s with the instance.
*/
static void enter(final IAdaptable adaptable) {
if (adaptable == null) {
throw new IllegalArgumentException(
"The given IAdaptable may not be null.");
}
// String indent = new String(new char[2 *
// history.size()]).replace('\0',
// '-');
// System.out
// .println(indent + "Entering all typed scopes for " + adaptable);
history.push(adaptable);
processEnter(adaptable);
}
/**
* Transitively leaves the {@link AdaptableScope} of all
* {@link IAdaptable}-compliant types (i.e. (super-)classes implementing
* {@link IAdaptable} and (super-)interfaces extending {@link IAdaptable})
* of the given {@link IAdaptable} for the given {@link IAdaptable}.
*
* @param adaptable
* The {@link IAdaptable} instance, for whose types to leave the
* {@link AdaptableScope}s with the instance. It has to be the
* adaptable for which a scope was last entered (and is used only
* to check the contract is preserved) .
*/
static void leave(final IAdaptable adaptable) {
if (adaptable == null) {
throw new IllegalArgumentException(
"The given IAdaptable may not be null.");
}
if (history.pop() != adaptable) {
throw new IllegalArgumentException(
"Only last entered scope may be left");
}
processLeave(adaptable);
if (!history.isEmpty()) {
// XXX: re-enter previous scope to restore state before last
// enter
processEnter(history.peek());
}
// String indent = new String(new char[2 *
// history.size()]).replace('\0',
// '-');
// System.out.println(indent + "Left all typed scopes for " +
// adaptable);
}
@SuppressWarnings("unchecked")
private static void process(Class<? extends IAdaptable> adaptableType,
final IAdaptable adaptableInstance, ScopeProcessor processor) {
processor.process(adaptableType, adaptableInstance);
// evaluate super classes of the given type
if (adaptableType.getSuperclass() != null && IAdaptable.class
.isAssignableFrom(adaptableType.getSuperclass())) {
process((Class<? extends IAdaptable>) adaptableType.getSuperclass(),
adaptableInstance, processor);
}
// evaluate interfaces of the given type
for (Class<?> interfaceType : adaptableType.getInterfaces()) {
if (IAdaptable.class.isAssignableFrom(interfaceType)) {
process((Class<? extends IAdaptable>) interfaceType,
adaptableInstance, processor);
}
}
}
private static void processEnter(final IAdaptable adaptable) {
// XXX: If the given adaptable is an adapter itself, recursively
// enter the scope of the adaptable it is bound to.
if (adaptable instanceof IAdaptable.Bound) {
IAdaptable boundTo = ((IAdaptable.Bound<?>) adaptable)
.getAdaptable();
if (boundTo != null) {
processEnter(boundTo);
}
}
process(adaptable.getClass(), adaptable, new ScopeProcessor() {
@Override
public void process(Class<? extends IAdaptable> adaptableType,
IAdaptable adaptableInstance) {
AdaptableScopes.<IAdaptable> typed(adaptableType)
.enter(adaptableInstance);
}
});
}
private static void processLeave(final IAdaptable adaptable) {
process(adaptable.getClass(), adaptable, new ScopeProcessor() {
@Override
public void process(Class<? extends IAdaptable> adaptableType,
IAdaptable adaptableInstance) {
AdaptableScopes.<IAdaptable> typed(adaptableType)
.leave(adaptableInstance);
}
});
// XXX: If the given adaptable is an adapter itself, recursively
// leave the scope the adaptable it is bound to.
if (adaptable instanceof IAdaptable.Bound) {
IAdaptable boundTo = ((IAdaptable.Bound<?>) adaptable)
.getAdaptable();
if (boundTo != null) {
processLeave(boundTo);
}
}
}
/**
* Retrieves an {@link AdaptableScope} for the given {@link IAdaptable}
* -compliant type.
*
* @param <A>
* The {@link IAdaptable} (sub-)type to return an
* {@link AdaptableScope} for.
*
* @param type
* The type of the {@link AdaptableScope}.
* @return The static {@link AdaptableScope} instance for the given type.
*/
@SuppressWarnings("unchecked")
public static <A extends IAdaptable> AdaptableScope<A> typed(
Class<? extends A> type) {
AdaptableScope<? extends IAdaptable> scope = scopes.get(type);
if (scope == null) {
// create singleton scope and register it
scope = new AdaptableScope<>(type);
scopes.put(type, scope);
}
return (AdaptableScope<A>) scope;
}
private AdaptableScopes() {
// should not be invoked by clients
}
}