Skip to content

Adapting to All Available Interfaces

Gary edited this page Mar 10, 2015 · 2 revisions

Table of Contents

This section goes into detail on how adaptation works for a specific example. This case is for DOM adapters, which are widely used in ATF, and shows how an adapter can be obtained for any interface the DOM adapter implements. It also points out what you need to do to make adaptation work in this example, which represents a common case.

Note: In this topic, "type" does not mean a Type as it does in most of this section. The word "type" refers to a type in the application data model the DOM is defined for, which is basically a DomNodeType.

For more details on the ATF DOM and how it uses adaptation, see DOM Adapters in the DOM in a Nutshell section.

Adaptation Example

The DOM adapter class LockingValidator used by the ATF State Chart Editor Sample contains this line in its OnNodeSet() method:

m_lockingContext = this.Cast<ILockingContext>(); // required ILockingContext

This code is attempting to get an adapter for LockingValidator to the interface ILockingContext. However, LockingValidator does not implement ILockingContext. How this cast can be made to work is what this section is about.

Creating a List of DOM Adapters

You can adapt a DOM adapter to all interfaces implemented by a set of DOM adapters by doing the following:

  • Define a set of DOM adapters for some type.
  • Create DomNodes for that type or initialize all the DOM adapters.
When you do this, you can adapt a DomNode defined for a type to any interface that any of the DOM adapters defined for that type implement.

Adding Adapter Creators

Under various circumstances, a list is created of all the DOM adapters for a given type represented by a DomNodeType. The method DomNodeType.AddCreator() adds the adapter to a list:

private void AddCreator(IAdapterCreator creator)
{
    if (m_adapterCreators == null)
        m_adapterCreators = new List<IAdapterCreator>();

    m_adapterCreators.Add(creator);
}

IAdapterCreator is an interface for adapter creators. In particular, IAdapterCreator is implemented by ExtensionAdapterCreator, which serves as a wrapper for adapters.

AddCreator() is called when the set of DOM adapters is frozen for a DomNodeType in the DomNodeType.FreezeExtensions() method. Here's the part of that method that calls AddCreator():

private void FreezeExtensions()
{
    ...
    foreach (ExtensionInfo extensionInfo in m_extensions)
    {
        // if this NodeType defines the extension, add an interface creator
        if (extensionInfo.DefiningType == this &&
            typeof(IAdapter).IsAssignableFrom(extensionInfo.Type))
        {
            AddCreator(new ExtensionAdapterCreator(extensionInfo));
        }
    }
}

The field m_extensions contains a list of all the extensions defined for a DomNodeType. If IAdapter is assignable from the type of the extension — and this includes every DOM adapter because DomNodeAdapter implements IAdapterAddCreator is called. ExtensionAdapterCreator serves as a container for the adapter. As a result, the field m_adapterCreators ends up listing all DOM adapters defined for that DomNodeType.

For instance, when a DomNode is created, FreezeExtensions() gets called for the DomNode's DomNodeType.

InitializeExtensions Method

It is also useful for applications to call DomNode.InitializeExtensions() after defining DOM adapters, also known as extensions. It is especially useful to do this for the root DomNode of the tree. A good time to do this is after opening a file and creating a DomNode tree from this application data.

When you call InitializeExtensions(), the OnNodeSet() method is called for each DOM adapter defined on the type of DomNode that InitializeExtensions() was invoked on. InitializeExtensions() also calls DomNodeType.FreezeExtensions() for every DomNodeType in the application, so a list of DOM adapters is created for every type.

Adaptation Actions

What exactly happens as a result of this call to Adapters.Cast<>():

m_lockingContext = this.Cast<ILockingContext>(); // required ILockingContext

Adapters.Cast<T>() calls Adapters.As<T>(), where adaptable is the instance of LockingValidator (this):

public static T Cast<T>(this IAdaptable adaptable)
        where T : class
{
    T converted = As<T>(adaptable);
    if (converted == null)
        throw new AdaptationException(typeof(T).Name + " adapter required");
    return converted;
}

In Adapters.As<T>(), adaptable is again the instance of LockingValidator and T is ILockingContext:

public static T As<T>(this IAdaptable adaptable)
    where T : class
{
    if (adaptable == null)
        return null;

    // try a normal cast
    var converted = adaptable as T;

    // if that fails, try to get an adapter
    if (converted == null)
        converted = adaptable.GetAdapter(typeof(T)) as T;

    return converted;
}

Here, the first attempt at adaptation (var converted = adaptable as T) results in null, because LockingValidator does not implement ILockingContext and so can't be adapted that way. Next, DomNodeAdapter.GetAdapter() is called, because LockingValidator (type of the parameter adaptable) is a DOM adapter:

public virtual object GetAdapter(Type type)
{
    return m_domNode.GetAdapter(type);
}

The parameter type is the type of ILockingContext. The field m_domNode contains the DomNode that is adapted to the DOM adapter. In this case, it is the root DOM node for which the DOM adapter LockingValidator is defined. As a result, DomNode.GetAdapter() is called next:

public object GetAdapter(Type type)
{
    // try a normal cast
    if (type.IsAssignableFrom(typeof(DomNode)))
        return this;

    // try to get an adapter
    return DomNodeType.GetAdapter(this, type);
}

The call type.IsAssignableFrom(typeof(DomNode)) returns false, because the type of ILockingContext is not assignable from the type of LockingValidatorLockingValidator does not implement ILockingContext. So DomNodeType.GetAdapter() is called:

internal static object GetAdapter(DomNode node, Type type)
{
    IEnumerable<IAdapterCreator> adapterCreators = GetAdapterCreators(node, type);
    foreach (IAdapterCreator creator in adapterCreators)
        return creator.GetAdapter(node, type);

    return null;
}

Diving deeper, DomNodeType.GetAdapterCreators() is called to try to get an adapter for the root DomNode from the list of adapter creators for type:

private static IEnumerable<IAdapterCreator> GetAdapterCreators(DomNode node, Type type)
{
    DomNodeType nodeType = node.Type;
    if (nodeType.m_adapterCreatorCache == null)
        nodeType.m_adapterCreatorCache = new Dictionary<Type, IEnumerable<IAdapterCreator>>();

    IEnumerable<IAdapterCreator> adapterCreators;
    if (!nodeType.m_adapterCreatorCache.TryGetValue(type, out adapterCreators))
    {
        // build an array of adapter creators that can adapt the node
        List<IAdapterCreator> creators = new List<IAdapterCreator>();
        while (nodeType != null)
        {
            if (nodeType.m_adapterCreators != null)
            {
                foreach (IAdapterCreator creator in nodeType.m_adapterCreators)
                {
                    if (creator.CanAdapt(node, type))
                        creators.Add(creator);
                }
            }

            nodeType = nodeType.BaseType;
        }

        // for empty arrays, use global instance
        adapterCreators = (creators.Count > 0) ? creators.ToArray() : EmptyEnumerable<IAdapterCreator>.Instance;

        // cache the result for subsequent searches
        node.Type.m_adapterCreatorCache.Add(type, adapterCreators);
    }

    return adapterCreators;
}

This method first sets nodeType to the DomNodeType of the DomNode for which the adapter is to be obtained, which is the type of the root DomNode in this case. If the method can't find an adapter for this type, it later resets the value to the type the original DomNode type is derived from, if any:

nodeType = nodeType.BaseType;

On the first pass through GetAdapterCreators(), the Dictionary m_adapterCreatorCache is null, so an empty dictionary is created. When this line is executed, the test condition is true, because the dictionary is empty:

if (!nodeType.m_adapterCreatorCache.TryGetValue(type, out adapterCreators))

So the subsequent block of code is entered and the while loop executed, starting with this line:

if (nodeType.m_adapterCreators != null)

Adding Adapter Creators described how the field m_adapterCreators in DomNodeType is populated with all the DOM adapters (each encapsulated in a ExtensionAdapterCreator) defined for that type. Therefore, GetAdapterCreators() iterates through all adapters defined for the root DomNodeType:

foreach (IAdapterCreator creator in nodeType.m_adapterCreators)

Recall that IAdapterCreator is an interface for adapter creators and is implemented by ExtensionAdapterCreator, which serves as a wrapper for adapters.

In the loop, it makes this test:

if (creator.CanAdapt(node, type))

The CanAdapt() method is in the private class ExtensionAdapterCreator and determines whether the adapter can be adapted to type, which is the type of ILockingContext:

public bool CanAdapt(object adaptee, Type type)
{
    DomNode node = adaptee as DomNode;
    return
        node != null &&
        type != null &&
        type.IsAssignableFrom(m_extensionInfo.Type);
}

The field m_adapterCreators is actually List<IAdapterCreator>, which is a list of ExtensionAdapterCreator instances in this case, because ExtensionAdapterCreator implements IAdapterCreator. In ExtensionAdapterCreator, the field m_extensionInfo holds the actual adapter.

IsAssignableFrom() tests whether the type of ILockingContext is assignable from the type of the adapter, m_extensionInfo.Type. This expression returns false until the adapter is EditingContext, which implements ILockingContext. This condition in DomNodeType.GetAdapterCreators() is finally satisfied, and the adapter EditingContext is added to the list:

if (creator.CanAdapt(node, type))
    creators.Add(creator);

EditingContext is the only adapter in StateChartEditor that meets this requirement, so when the loop ends, EditingContext is the only adapter in the list.

From this point, control goes back up the stack:

  • DomNodeType.GetAdapterCreators returns an adapter list containing EditingContext.
  • DomNodeType.GetAdapter() returns EditingContext.
  • DomNode.GetAdapter() returns EditingContext.
  • DomNodeAdapter.GetAdapter() returns EditingContext.
  • Adapters.As<T> returns EditingContext.
  • Adapters.Cast<T> returns EditingContext, which is where this all started:
m_lockingContext = this.Cast<ILockingContext>(); // required ILockingContext

The field m_lockingContext is set to the DOM adapter EditingContext, which implements ILockingContext, so it fills the bill.

Topics in this section

Clone this wiki locally