-
Notifications
You must be signed in to change notification settings - Fork 263
Adapting to All Available Interfaces
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.
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.
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
DomNode
s for that type or initialize all the DOM adapters.
DomNode
defined for a type to any interface that any of the DOM adapters defined for that type implement.
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 IAdapter
— AddCreator
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
.
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.
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 LockingValidator
—LockingValidator
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 containingEditingContext
. -
DomNodeType.GetAdapter()
returnsEditingContext
. -
DomNode.GetAdapter()
returnsEditingContext
. -
DomNodeAdapter.GetAdapter()
returnsEditingContext
. -
Adapters.As<T>
returnsEditingContext
. -
Adapters.Cast<T>
returnsEditingContext
, 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.
- What is Adaptation: General discussion of what adaptation is and how it is used in ATF.
- General Adaptation Interfaces: Survey of interfaces for adaptation.
- General Adaptation Classes: Describe fundamental classes in adaptation.
- Control Adapters: Discussion of control adapters, which add abilities to controls without changing the control.
- Other Adaptation Classes: Survey of non-control adapter classes that perform various kinds of adaptation.
- Adapting to All Available Interfaces: A detailed example of how adaptation works and what you need to do to make it work showing how adapters can be obtained for any interface DOM adapters implement.
- Home
- Getting Started
- Features & Benefits
- Requirements & Dependencies
- Gallery
- Technology & Samples
- Adoption
- News
- Release Notes
- ATF Community
- Searching Documentation
- Using Documentation
- Videos
- Tutorials
- How To
- Programmer's Guide
- Reference
- Code Samples
- Documentation Files
© 2014-2015, Sony Computer Entertainment America LLC