Skip to content

General Adaptation Classes

gstaas edited this page Nov 7, 2014 · 2 revisions

Table of Contents

The namespace Sce.Atf.Adaptation provides several classes fundamental to adaptation.

Adapters Class

The Adapters class contains the key adaptation methods that actually get adapters. Adapters provides these methods as extension methods. These extension methods are on the base class object, so they apply to any object. Simply adding using Sce.Atf.Adaptation; to a file makes these extension methods available.

As Methods

The As() methods are the adaptation workhorses, doing the basic work of finding adapters and called by all the other Adapters methods. They take the following forms, all invoked on the object to be adapted:

  • object As(this object reference, Type type): Parameter specifies the type.
  • T As<T>(this object reference): Type is the generic parameter.
  • T As<T>(this IAdaptable adaptable): Type is the generic parameter; invoked on object implementing IAdaptable.
The As() method attempts to return an adapter for the object it is invoked on. Examining this method in detail illuminates how adaptation works. Here's the entire method (link to source):
public static object As(this object reference, Type type)
{
    if (reference == null)
        return null;

    if (type == null)
        throw new ArgumentNullException("type");

    // is the adapted object compatible?
    if (type.IsAssignableFrom(reference.GetType()))
        return reference;

    // try to get an adapter
    var adaptable = reference as IAdaptable;
    if (adaptable != null)
    {
        object adapter = adaptable.GetAdapter(type);
        if (adapter != null)
            return adapter;
    }

    return null;
}

After checking parameters, As() calls the System.Type method type.IsAssignableFrom(reference.GetType()) to determine whether an object of the given type is assignable from an object of the type of reference. For this case, the comments on bool IsAssignableFrom(Type) say it returns true if any of these are true:

  • reference.GetType() and type represent the same type.
  • type is in the inheritance hierarchy of reference.GetType(), that is, reference.GetType() derives from type through a derivation hierarchy.
  • type is an interface that reference.GetType() implements.
  • reference.GetType() is a generic type parameter and type represents one of the constraints of reference.GetType().
The IsAssignableFrom() method returns false if none of these conditions are true or reference.GetType() is null. These conditions spell out the type compatibility for the assignment.

When type.IsAssignableFrom(reference.GetType()) is true, an object of type can be assigned from an object of type reference. In this case, the object reference itself is returned, so the object serves as its own adapter. In other words, reference can be cast as type.

If casting is not possible, the method then tries to get an adapter for reference. It can only do this when reference implements IAdaptable, so GetAdapter() can be invoked on reference to get an adapter to return. Failing this, As() returns null to indicate no adapter is available.

This highlights how ATF's adaptation goes beyond what the C# as operator offers, because type compatibility is not required. If casting fails, the As() method looks for an adapter for the object it is invoked on. You define the adapter that is returned in your reference object's implementation of IAdaptable.GetAdapter(), so adaptation is under your control.

The method As<T>(this object reference) works similarly (link to source):

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

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

    // if that fails, try to get an adapter
    if (converted == null)
    {
        var adaptable = reference as IAdaptable;
        if (adaptable != null)
            converted = adaptable.GetAdapter(typeof(T)) as T;
    }

    return converted;
}

After parameter checks, the method tries the cast var converted = reference as T;. If that fails, it attempts to get an adapter. Again, this requires that reference implement IAdaptable.

The method As<T>(this IAdaptable adaptable) is almost the same as the other As() methods.

Note that the As() methods always return an object of the type requested, or a type that is assignable from that type.

Cast Methods

The Cast() methods are almost the same as their As() counterparts, returning an adapter, and are implemented by calling As() methods. The only difference is that if no adapter is found, they raise an exception instead of returning null. The available forms are similar to As():

  • object Cast(this object reference, Type type): Parameter specifies the type.
  • T Cast<T>(this object reference): Type is the generic parameter.
  • T Cast<T>(this IAdaptable adaptable): Type is the generic parameter; invoked on object implementing IAdaptable.
Like As() methods, Cast() methods always return an object of the type requested, or a type that is assignable from that type.

Is Methods

Is() methods simply determine whether an adapter is available. The are implemented by calling the appropriate As() method and checking whether null is returned or not. Their forms are similar to As() and Cast(), and all return true if and only if the object is adaptable to the given type:

  • bool Is(this object reference, Type type): Parameter specifies the type.
  • bool Is<T>(this object reference): Type is the generic parameter.
  • bool Is<T>(this IAdaptable adaptable): Type is the generic parameter; invoked on object implementing IAdaptable.
For example, suppose the following classes are defined:
public class Prince
{
    ...
}

public class PrinceMorph : Prince
{
    ...
}

public void Test()
{
    Prince Ralph;
    PrinceMorph Frog = null;
    Ralph = Frog;
    if (Frog.Is<Prince>())
    {
        ...
    }
}

Because Frog's class derives from Prince, Prince is assignable from Frog, and the assignment above is valid. So, in this case, the answer to the question Frog.Is<Prince>()? is true.

IEnumerable Adaption Methods

Some methods in Adapters work on an IEnumerable of adapters in a variety of ways:

  • IEnumerable<object> AsAll(this object reference, Type type): Get all adapters that can convert a reference to the given type.
  • IEnumerable<T> AsAll<T>(this object reference): Get all adapters that can convert a reference to the given type T.
  • IEnumerable<T> AsAll<T>(this IDecoratable decoratable): Get an enumeration of all adapters that can convert a reference to the given type.
  • IEnumerable<object> AsIEnumerable(this IEnumerable enumerable, Type type): Get an adapter that converts an enumerable to an enumerable of another type.
  • IEnumerable<T> AsIEnumerable<T>(this IEnumerable enumerable): Return an enumeration for an adapter that converts an enumerable to an enumerable of another type T.
  • bool Any(this IEnumerable enumerable, Type type): Returns whether any of the items in the enumerable are adaptable to the given type.
  • bool Any<T>(this IEnumerable enumerable): Return whether any of the items in the enumerable are adaptable to the given type T.
  • bool All(this IEnumerable enumerable, Type type): Return whether all of the items in the enumerable are adaptable to the given type.
  • bool All<T>(this IEnumerable enumerable): Return whether all of the items in the enumerable are adaptable to the given type T.

Using Adaptation Methods

The adaptation methods in Adapters are widely used in ATF, especially for the ATF DOM. It is very common to use the As<>T() and Cast<>T() methods to adapt a DomNode to another type for which it has a DOM adapter—an object that adapts the DomNode to the specified type. The fundamental DOM adapter class DomNodeAdapter implements the adaptation interfaces previously discussed:

public abstract class DomNodeAdapter : IAdapter, IAdaptable, IDecoratable

Creating adapters in the DOM follows this development sequence:

  1. A type is defined, typically in a type definition file. The ATF DOM provides excellent support for type definitions in the XML Schema Definition (XSD) language.
  2. The XML schema is loaded, if one is used.
  3. A DOM adapter is defined for types whose data is represented by a DomNode in the application data.
  4. The DOM adapter is initialized to its DomNode by calling any of the Adapters methods As<>T(), Cast<>T(), or Is<>T(). The returned value from As<>T() or Cast<>T() can then be used with the API of the DOM adapter and holds the application data of the DomNode, because it is still a DomNode.
For example, the ATF Simple DOM No XML Editor Sample uses this line to treat a document as a context:
EventSequenceContext context = Adapters.As<EventSequenceContext>(document);

EventSequenceContext is a DOM adapter derived from EditingContext that ultimately derives from DomNodeAdapter:

EventSequenceContext : EditingContext

EventSequenceContext is also defined as a DOM adapter for the root type of the data, "eventSequenceType", which is also the type of the document:

Schema.eventSequenceType.Type.Define(new ExtensionInfo<EventSequenceContext>());

For details on using adaptation in the DOM, see DOM Adapters in the DOM in a Nutshell section. For the full reference, see the ATF Programmer’s Guide: Document Object Model (DOM), which you can download at ATF Documentation.

Adapter Class

Adapter provides an example of an adapter and is used as an adapter a few places in ATF:

public class Adapter : IAdapter, IAdaptable, IDecoratable

In addition to IAdapter, Adapter implements IAdaptable and IDecoratable. (This is more of a reminder than a requirement, because IAdapter implements both of these anyway.)

The IAdapter.Adaptee property gets and sets the adaptee, that is, the object being adapted (link to source):

public object Adaptee
{
    get { return m_adaptee; }
    set
    {
        if (m_adaptee != value)
        {
            object oldAdaptee = m_adaptee;
            m_adaptee = value;
            OnAdapteeChanged(oldAdaptee);
        }
    }
}

The Adapter class gets adapters using the methods in Adapters. For details on Adapters methods, see Adapters Class.

Adapter's casting methods (As<>T(), Cast<>T(), and Is<>T()) simply use the corresponding Adapters methods. For example, As<T>() calls Adapters.As<T>() (link to source):

public T As<T>()
    where T : class
{
    return Adapters.As<T>(this);
}

The IAdaptable.GetAdapter() method is more involved than the casting methods (link to source):

public object GetAdapter(Type type)
{
    // see if this can adapt
    object adapter = Adapt(type);

    // if not, let the adaptee handle it
    if (adapter == null)
        adapter = m_adaptee.As(type);

    return adapter;
}

GetAdapter() first tries the Adapt() method to determine if the object itself can serve as an adapter (link to source):

protected virtual object Adapt(Type type)
{
    // default is to return this if compatible with requested type
    if (type.IsAssignableFrom(GetType()))
        return this;

    return null;
}

Adapt() calls type.IsAssignableFrom(GetType()) to determine whether an object of the given type is assignable from an object of the type of the Adapter object. When IsAssignableFrom() is true, Adapt() returns this, the object itself. GetAdapter() returns this in turn.

If the object can't serve as its own adapter, GetAdapter() makes this assignment:

adapter = m_adaptee.As(type);

m_adaptee.As(type) invokes Adapters.As() on the adaptee (the object being adapted) because:

  • m_adaptee holds the Adaptee property value.
  • Adapters.As() is an extension method on object.
  • Adapter is in the Sce.Atf.Adaptation namespace, same as Adapters.
Here's another example, IDecoratable.GetDecorators() returning an IEnumerable of adapters (link to source):
public IEnumerable<object> GetDecorators(Type type)
{
    // see if this can adapt
    object adapter = Adapt(type);
    if (adapter != null)
        yield return adapter;

    foreach (object obj in m_adaptee.AsAll(type))
        if (obj != adapter)
            yield return obj;
}

Classes On Collections

Several Sce.Atf.Adaptation classes operate on collections:

  • AdaptableActiveCollection: Collection representing active items that uses adaptation on items implementing IAdaptable to convert them to other types. It's used by both the ContextRegistry and DocumentRegistry components.
  • AdaptableCollection: Wrap an ICollection of one type to implement an ICollection adapted to another type.
  • AdaptableList: Wrap an IList of one type to implement an IList adapted to another type.
  • AdaptableSelection: Collection representing a selection. It uses adaptation to convert to other types. It is used in Sce.Atf.Dom.EditingContext from which several samples derive editing contexts, as well as in Sce.Atf.Dom.SelectionContext.

AdapterCreator Class

AdapterCreator implements IAdapterCreator and appears in all the samples that use the ATF DOM in a line like this:

DomNodeType.BaseOfAllTypes.AddAdapterCreator(new AdapterCreator<CustomTypeDescriptorNodeAdapter>());

AdapterCreator is used here to make property editors show the attributes listed in the property descriptor definitions. For more information, see Metadata Driven Property Editing.

AdaptablePath Class

AdaptablePath represents a path in a tree or graph, such as a DomNode tree representing application data. AdaptablePath can adapt the last element of the path to a requested type. For example, DOM property descriptors (like AttributePropertyDescriptor) try to adapt an AdaptablePath object to a DomNode, and this succeeds if the last element of the path can be adapted to a DomNode. AdaptablePath is used in various classes that use paths in trees and lists.

Topics in this section

Clone this wiki locally