diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 2ad6d69..54ba225 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -9,7 +9,6 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Entity\Plugin\DataType\EntityReference;
-use Drupal\Core\Entity\TypedData\EntityDataDefinition;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
@@ -131,13 +130,6 @@
   protected $entityKeys = array();
 
   /**
-   * The instantiated entity data definition.
-   *
-   * @var \Drupal\Core\Entity\TypedData\EntityDataDefinition
-   */
-  protected $dataDefinition;
-
-  /**
    * Overrides Entity::__construct().
    */
   public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
@@ -261,98 +253,8 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco
   /**
    * {@inheritdoc}
    */
-  public function getDataDefinition() {
-    if (!$this->dataDefinition) {
-      $this->dataDefinition = EntityDataDefinition::create($this->getEntityTypeId());
-      $this->dataDefinition->setBundles(array($this->bundle()));
-    }
-    return $this->dataDefinition;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getValue() {
-    // @todo: This does not make much sense, so remove once TypedDataInterface
-    // is removed. See https://drupal.org/node/2002138.
-    return $this->toArray();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setValue($value, $notify = TRUE) {
-    // @todo: This does not make much sense, so remove once TypedDataInterface
-    // is removed. See https://drupal.org/node/2002138.
-    foreach ($value as $field_name => $field_value) {
-      $this->set($field_name, $field_value, $notify);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getString() {
-    return (string) $this->label();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function validate() {
-    return $this->typedDataManager()->getValidator()->validate($this);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function applyDefaultValue($notify = TRUE) {
-    foreach ($this->getProperties() as $property) {
-      $property->applyDefaultValue(FALSE);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConstraints() {
-    return array();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getName() {
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRoot() {
-    return $this;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPropertyPath() {
-    return '';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getParent() {
-    return NULL;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setContext($name = NULL, TypedDataInterface $parent = NULL) {
-    // As entities are always the root of the tree of typed data, we do not need
-    // to set any parent or name.
+    return $this->getTypedData()->validate();
   }
 
   /**
@@ -376,7 +278,6 @@ public function __sleep() {
     }
     $this->fields = array();
     $this->fieldDefinitions = NULL;
-    $this->dataDefinition = NULL;
     $this->clearTranslationCache();
 
     return parent::__sleep();
@@ -413,11 +314,11 @@ public function hasField($field_name) {
   /**
    * {@inheritdoc}
    */
-  public function get($property_name) {
-    if (!isset($this->fields[$property_name][$this->activeLangcode])) {
-      return $this->getTranslatedField($property_name, $this->activeLangcode);
+  public function get($field_name) {
+    if (!isset($this->fields[$field_name][$this->activeLangcode])) {
+      return $this->getTranslatedField($field_name, $this->activeLangcode);
     }
-    return $this->fields[$property_name][$this->activeLangcode];
+    return $this->fields[$field_name][$this->activeLangcode];
   }
 
   /**
@@ -452,7 +353,8 @@ protected function getTranslatedField($name, $langcode) {
         if (isset($this->values[$name][$langcode])) {
           $value = $this->values[$name][$langcode];
         }
-        $field = \Drupal::typedDataManager()->getPropertyInstance($this, $name, $value);
+        $entity_adapter = $this->getTypedData();
+        $field = \Drupal::typedDataManager()->getPropertyInstance($entity_adapter, $name, $value);
         if ($default) {
           // $this->defaultLangcode might not be set if we are initializing the
           // default language code cache, in which case there is no valid
@@ -481,21 +383,21 @@ public function set($name, $value, $notify = TRUE) {
   /**
    * {@inheritdoc}
    */
-  public function getProperties($include_computed = FALSE) {
-    $properties = array();
+  public function getFields($include_computed = FALSE) {
+    $fields = array();
     foreach ($this->getFieldDefinitions() as $name => $definition) {
       if ($include_computed || !$definition->isComputed()) {
-        $properties[$name] = $this->get($name);
+        $fields[$name] = $this->get($name);
       }
     }
-    return $properties;
+    return $fields;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getIterator() {
-    return new \ArrayIterator($this->getProperties());
+    return new \ArrayIterator($this->getFields());
   }
 
   /**
@@ -525,7 +427,7 @@ public function getFieldDefinitions() {
    */
   public function toArray() {
     $values = array();
-    foreach ($this->getProperties() as $name => $property) {
+    foreach ($this->getFields() as $name => $property) {
       $values[$name] = $property->getValue();
     }
     return $values;
@@ -534,21 +436,6 @@ public function toArray() {
   /**
    * {@inheritdoc}
    */
-  public function isEmpty() {
-    if (!$this->isNew()) {
-      return FALSE;
-    }
-    foreach ($this->getProperties() as $property) {
-      if ($property->getValue() !== NULL) {
-        return FALSE;
-      }
-    }
-    return TRUE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
     if ($operation == 'create') {
       return $this->entityManager()
@@ -644,8 +531,6 @@ public function onChange($name) {
 
   /**
    * {@inheritdoc}
-   *
-   * @return \Drupal\Core\Entity\ContentEntityInterface
    */
   public function getTranslation($langcode) {
     // Ensure we always use the default language code when dealing with the
@@ -826,15 +711,6 @@ public function getTranslationLanguages($include_default = TRUE) {
   }
 
   /**
-   * Overrides Entity::translations().
-   *
-   * @todo: Remove once Entity::translations() gets removed.
-   */
-  public function translations() {
-    return $this->getTranslationLanguages(FALSE);
-  }
-
-  /**
    * Updates the original values with the interim changes.
    */
   public function updateOriginalValues() {
@@ -957,8 +833,6 @@ public function createDuplicate() {
       $duplicate->{$entity_type->getKey('revision')}->value = NULL;
     }
 
-    $duplicate->entityKeys = array();
-
     return $duplicate;
   }
 
@@ -969,6 +843,8 @@ public function __clone() {
     // Avoid deep-cloning when we are initializing a translation object, since
     // it will represent the same entity, only with a different active language.
     if (!$this->translationInitialize) {
+      // The translation is a different object, and needs its own TypedData object.
+      $this->typedData = NULL;
       $definitions = $this->getFieldDefinitions();
       foreach ($this->fields as $name => $values) {
         $this->fields[$name] = array();
@@ -981,7 +857,7 @@ public function __clone() {
         }
         foreach ($values as $langcode => $items) {
           $this->fields[$name][$langcode] = clone $items;
-          $this->fields[$name][$langcode]->setContext($name, $this);
+          $this->fields[$name][$langcode]->setContext($name, $this->getTypedData());
         }
       }
 
@@ -1015,11 +891,11 @@ public function referencedEntities() {
     $referenced_entities = array();
 
     // Gather a list of referenced entities.
-    foreach ($this->getProperties() as $field_items) {
+    foreach ($this->getFields() as $field_items) {
       foreach ($field_items as $field_item) {
         // Loop over all properties of a field item.
         foreach ($field_item->getProperties(TRUE) as $property) {
-          if ($property instanceof EntityReference && $entity = $property->getTarget()) {
+          if ($property instanceof EntityReference && $entity = $property->getValue()) {
             $referenced_entities[] = $entity;
           }
         }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
index 249c302..fac4591 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\Entity;
 
-use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\Core\TypedData\TranslatableInterface;
 
 /**
@@ -28,7 +27,7 @@
  *
  * @ingroup entity_api
  */
-interface ContentEntityInterface extends EntityInterface, RevisionableInterface, TranslatableInterface, ComplexDataInterface {
+interface ContentEntityInterface extends EntityInterface, RevisionableInterface, TranslatableInterface {
 
   /**
    * Marks the translation identified by the given language code as existing.
@@ -152,4 +151,67 @@ public function getFieldDefinitions();
    */
   public function toArray();
 
+  /**
+   * Gets a field item list.
+   *
+   * @param string $field_name
+   *   The name of the field to get; e.g., 'title' or 'name'.
+   *
+   * @throws \InvalidArgumentException
+   *   If an invalid field name is given.
+   *
+   * @return \Drupal\Core\Field\FieldItemListInterface
+   *   The field item list, containing the field items.
+   */
+  public function get($field_name);
+
+  /**
+   * Sets a field value.
+   *
+   * @param string $field_name
+   *   The name of the field to set; e.g., 'title' or 'name'.
+   * @param mixed $value
+   *   The value to set, or NULL to unset the field.
+   * @param bool $notify
+   *   (optional) Whether to notify the entity of the change. Defaults to
+   *   TRUE. If the update stems from the entity, set it to FALSE to avoid
+   *   being notified again.
+   *
+   * @throws \InvalidArgumentException
+   *   If the specified field does not exist.
+   *
+   * @return $this
+   */
+  public function set($field_name, $value, $notify = TRUE);
+
+  /**
+   * Gets an array of field item lists.
+   *
+   * @param bool $include_computed
+   *   If set to TRUE, computed fields are included. Defaults to FALSE.
+   *
+   * @return \Drupal\Core\Field\FieldItemListInterface[]
+   *   An array of field item lists implementing, keyed by field name.
+   */
+  public function getFields($include_computed = FALSE);
+
+  /**
+   * Reacts to changes to a field.
+   *
+   * Note that this is invoked after any changes have been applied.
+   *
+   * @param string $field_name
+   *   The name of the field which is changed.
+   */
+  public function onChange($field_name);
+
+  /**
+   * Validates the currently set values.
+   *
+   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+   *   A list of constraint violations. If the list is empty, validation
+   *   succeeded.
+   */
+  public function validate();
+
 }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 6d75946..07d2efa 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -195,7 +195,7 @@ protected function invokeTranslationHooks(ContentEntityInterface $entity) {
   protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
     foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
       $translation = $entity->getTranslation($langcode);
-      foreach ($translation->getProperties(TRUE) as $field) {
+      foreach ($translation->getFields(TRUE) as $field) {
         $field->$method();
       }
     }
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 51737de..6778836 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -12,8 +12,6 @@
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException;
-use Drupal\Core\Entity\Exception\AmbiguousEntityClassException;
-use Drupal\Core\Entity\Exception\NoCorrespondingEntityClassException;
 use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
@@ -24,7 +22,10 @@
  * Defines a base entity class.
  */
 abstract class Entity implements EntityInterface {
-  use DependencySerializationTrait;
+
+  use DependencySerializationTrait {
+    __sleep as traitSleep;
+  }
 
   /**
    * The entity type.
@@ -41,6 +42,13 @@
   protected $enforceIsNew;
 
   /**
+   * A typed data object wrapping this entity.
+   *
+   * @var \Drupal\Core\TypedData\ComplexDataInterface
+   */
+  protected $typedData;
+
+  /**
    * Constructs an Entity object.
    *
    * @param array $values
@@ -547,4 +555,23 @@ public function toArray() {
     return array();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypedData() {
+    if (!isset($this->typedData)) {
+      $class = \Drupal::typedDataManager()->getDefinition('entity')['class'];
+      $this->typedData = $class::createFromEntity($this);
+    }
+    return $this->typedData;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __sleep() {
+    $this->typedData = NULL;
+    return $this->traitSleep();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index f60b635..5063d66 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -362,6 +362,19 @@ public function setOriginalId($id);
   public function toArray();
 
   /**
+   * Returns a typed data object for this entity object.
+   *
+   * The returned typed data object wraps this entity and allows dealing with
+   * entities based on the generic typed data API.
+   *
+   * @return \Drupal\Core\TypedData\ComplexDataInterface
+   *   The typed data object for this entity.
+   *
+   * @see \Drupal\Core\TypedData\TypedDataInterface
+   */
+  public function getTypedData();
+
+  /**
    * The unique cache tag associated with this entity.
    *
    * @return array
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
index dc54e75..0c80fcf 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php
@@ -83,7 +83,6 @@ public function getDerivativeDefinitions($base_plugin_definition) {
     foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
       $this->derivatives[$entity_type_id] = array(
         'label' => $entity_type->getLabel(),
-        'class' => $entity_type->getClass(),
         'constraints' => array('EntityType' => $entity_type_id),
       ) + $base_plugin_definition;
 
@@ -92,7 +91,6 @@ public function getDerivativeDefinitions($base_plugin_definition) {
         if ($bundle !== $entity_type_id) {
           $this->derivatives[$entity_type_id . ':' . $bundle] = array(
             'label' => $bundle_info['label'],
-            'class' => $entity_type->getClass(),
             'constraints' => array(
               'EntityType' => $entity_type_id,
               'Bundle' => $bundle,
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Entity.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Entity.php
deleted file mode 100644
index 5755c51..0000000
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Entity.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Entity\Plugin\DataType\Entity.
- */
-
-namespace Drupal\Core\Entity\Plugin\DataType;
-
-/**
- * Defines the base plugin for deriving data types for entity types.
- *
- * Note that the class only registers the plugin, and is actually never used.
- * \Drupal\Core\Entity\Entity is available for use as base class.
- *
- * @DataType(
- *   id = "entity",
- *   label = @Translation("Entity"),
- *   description = @Translation("All kind of entities, e.g. nodes, comments or users."),
- *   deriver = "\Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver",
- *   definition_class = "\Drupal\Core\Entity\TypedData\EntityDataDefinition"
- * )
- */
-abstract class Entity {
-
-}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php
new file mode 100644
index 0000000..800538d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Plugin\DataType\Entity.
+ */
+
+namespace Drupal\Core\Entity\Plugin\DataType;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\TypedData\EntityDataDefinition;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\Exception\MissingDataException;
+use Drupal\Core\TypedData\TypedData;
+
+/**
+ * Defines the "entity" data type.
+ *
+ * Instances of this class wrap entity objects and allow to deal with entities
+ * based upon the Typed Data API.
+ *
+ * In addition to the "entity" data type, this exposes derived
+ * "entity:$entity_type" and "entity:$entity_type:$bundle" data types.
+ *
+ * @DataType(
+ *   id = "entity",
+ *   label = @Translation("Entity"),
+ *   description = @Translation("All kind of entities, e.g. nodes, comments or users."),
+ *   deriver = "\Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver",
+ *   definition_class = "\Drupal\Core\Entity\TypedData\EntityDataDefinition"
+ * )
+ */
+class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexDataInterface {
+
+  /**
+   * The wrapped entity object.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface|null
+   */
+  protected $entity;
+
+  /**
+   * Creates an instance wrapping the given entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface|null $entity
+   *   The entity object to wrap.
+   *
+   * @return static
+   */
+  public static function createFromEntity(EntityInterface $entity) {
+    $definition = EntityDataDefinition::create()
+      ->setEntityTypeId($entity->getEntityTypeId())
+      ->setBundles([ $entity->bundle() ]);
+    $instance = new static($definition);
+    $instance->setValue($entity);
+    return $instance;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue() {
+    return $this->entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValue($entity, $notify = TRUE) {
+    $this->entity = $entity;
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($property_name) {
+    if (!isset($this->entity)) {
+      throw new MissingDataException(String::format('Unable to get property @name as no entity has been provided.', array('@name' => $property_name)));
+    }
+    if (!$this->entity instanceof ContentEntityInterface) {
+      // @todo: Add support for config entities in
+      // https://www.drupal.org/node/1818574.
+      throw new \InvalidArgumentException(String::format('Unable to get unknown property @name.', array('@name' => $property_name)));
+    }
+    // This will throw an exception for unknown fields.
+    return $this->entity->get($property_name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($property_name, $value, $notify = TRUE) {
+    if (!isset($this->entity)) {
+      throw new MissingDataException(String::format('Unable to set property @name as no entity has been provided.', array('@name' => $property_name)));
+    }
+    if (!$this->entity instanceof ContentEntityInterface) {
+      // @todo: Add support for config entities in
+      // https://www.drupal.org/node/1818574.
+      throw new \InvalidArgumentException(String::format('Unable to set unknown property @name.', array('@name' => $property_name)));
+    }
+    // This will throw an exception for unknown fields.
+    return $this->entity->set($property_name, $value, $notify);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProperties($include_computed = FALSE) {
+    if (!isset($this->entity)) {
+      throw new MissingDataException(String::format('Unable to get properties as no entity has been provided.'));
+    }
+    if (!$this->entity instanceof ContentEntityInterface) {
+      // @todo: Add support for config entities in
+      // https://www.drupal.org/node/1818574.
+      return array();
+    }
+    return $this->entity->getFields($include_computed);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toArray() {
+    if (!isset($this->entity)) {
+      throw new MissingDataException(String::format('Unable to get property values as no entity has been provided.'));
+    }
+    return $this->entity->toArray();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return !isset($this->entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($property_name) {
+    if (isset($this->entity) && $this->entity instanceof ContentEntityInterface) {
+      // Let the entity know of any changes.
+      $this->entity->onChange($property_name);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDataDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getString() {
+    return isset($this->entity) ? $this->entity->label() : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applyDefaultValue($notify = TRUE) {
+    // Apply the default value of all properties.
+    foreach ($this->getProperties() as $property) {
+      $property->applyDefaultValue(FALSE);
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->getProperties());
+    return isset($this->entity) ? $this->entity->getIterator() : new \ArrayIterator([]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php
index a4e8940..3ecce46 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php
@@ -60,9 +60,9 @@ public function getTargetDefinition() {
    */
   public function getTarget() {
     if (!isset($this->target) && isset($this->id)) {
-      // If we have a valid reference, return the entity object which is typed
-      // data itself.
-      $this->target = entity_load($this->getTargetDefinition()->getEntityTypeId(), $this->id);
+      // If we have a valid reference, return the entity's TypedData adapter.
+      $entity = entity_load($this->getTargetDefinition()->getEntityTypeId(), $this->id);
+      $this->target = isset($entity) ? $entity->getTypedData() : NULL;
     }
     return $this->target;
   }
@@ -82,22 +82,17 @@ public function getTargetIdentifier() {
   /**
    * {@inheritdoc}
    */
-  public function getValue() {
-    // Entities are already typed data, so just return that.
-    return $this->getTarget();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function setValue($value, $notify = TRUE) {
     unset($this->target);
     unset($this->id);
 
     // Both the entity ID and the entity object may be passed as value. The
     // reference may also be unset by passing NULL as value.
-    if (!isset($value) || $value instanceof EntityInterface) {
-      $this->target = $value;
+    if (!isset($value)) {
+      $this->target = NULL;
+    }
+    elseif ($value instanceof EntityInterface) {
+      $this->target = $value->getTypedData();
     }
     elseif (!is_scalar($value) || $this->getTargetDefinition()->getEntityTypeId() === NULL) {
       throw new \InvalidArgumentException('Value is not a valid entity.');
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
index 196dab9..746eb95 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/BundleConstraintValidator.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
 
+use Drupal\Core\TypedData\TypedDataInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -16,11 +17,20 @@
 class BundleConstraintValidator extends ConstraintValidator {
 
   /**
-   * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
+   * {@inheritdoc}
    */
   public function validate($entity, Constraint $constraint) {
-    if (!empty($entity) && !in_array($entity->bundle(), $constraint->getBundleOption())) {
+    if (!isset($entity)) {
+      return;
+    }
+    // The constraint is used on both entity references and typed data entity
+    // adapters. For the latter, we need to unwrap the actual entity.
+    if ($entity instanceof TypedDataInterface) {
+      $entity = $entity->getValue();
+    }
+    if (!in_array($entity->bundle(), $constraint->getBundleOption())) {
       $this->context->addViolation($constraint->message, array('%bundle' => implode(', ', $constraint->getBundleOption())));
     }
   }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
index 5e316ca..a74f20d 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -20,10 +20,18 @@ class EntityTypeConstraintValidator extends ConstraintValidator {
    * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
    */
   public function validate($entity, Constraint $constraint) {
-
+    if (!isset($entity)) {
+      return;
+    }
+    // The constraint is used on both entity references and typed data entity
+    // adapters. For the latter, we need to unwrap the actual entity.
+    if ($entity instanceof TypedDataInterface) {
+      $entity = $entity->getValue();
+    }
     /** @var $entity \Drupal\Core\Entity\EntityInterface */
-    if (!empty($entity) && $entity->getEntityTypeId() != $constraint->type) {
+    if ($entity->getEntityTypeId() != $constraint->type) {
       $this->context->addViolation($constraint->message, array('%type' => $constraint->type));
     }
   }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php
index bc269c5..75cc152 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php
@@ -27,7 +27,7 @@ public function validate($value, Constraint $constraint) {
     if (empty($id)) {
       return;
     }
-    $referenced_entity = $value->get('entity')->getTarget();
+    $referenced_entity = $value->get('entity')->getValue();
     if (!$referenced_entity) {
       $type = $value->getFieldDefinition()->getSetting('target_type');
       $this->context->addViolation($constraint->message, array('%type' => $type, '%id' => $id));
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index af81e10..5b21b6b 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -1632,7 +1632,8 @@ protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definit
 
     // Create field item objects and return.
     foreach ($items_by_entity as $revision_id => $values) {
-      $items_by_entity[$revision_id] = \Drupal::typedDataManager()->create($field_definition, $values, $field_definition->getName(), $entities[$revision_id]);
+      $entity_adapter = $entities[$revision_id]->getTypedData();
+      $items_by_entity[$revision_id] = \Drupal::typedDataManager()->create($field_definition, $values, $field_definition->getName(), $entity_adapter);
     }
     return $items_by_entity;
   }
diff --git a/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php b/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php
index c2268c8..30a63a2 100644
--- a/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php
+++ b/core/lib/Drupal/Core/Entity/TypedData/EntityDataDefinition.php
@@ -57,9 +57,9 @@ public static function createFromDataType($data_type) {
   public function getPropertyDefinitions() {
     if (!isset($this->propertyDefinitions)) {
       if ($entity_type_id = $this->getEntityTypeId()) {
-        // Return an empty array for entity types that don't support typed data.
+        // Return an empty array for entities that are not content entities.
         $entity_type_class = \Drupal::entityManager()->getDefinition($entity_type_id)->getClass();
-        if (!in_array('Drupal\Core\TypedData\TypedDataInterface', class_implements($entity_type_class))) {
+        if (!in_array('Drupal\Core\Entity\ContentEntityInterface', class_implements($entity_type_class))) {
           $this->propertyDefinitions = array();
         }
         else {
@@ -89,9 +89,13 @@ public function getDataType() {
     $type = 'entity';
     if ($entity_type = $this->getEntityTypeId()) {
       $type .= ':' . $entity_type;
-      // Append the bundle only if we know it for sure.
+      // Append the bundle only if we know it for sure and it is not the default
+      // bundle.
       if (($bundles = $this->getBundles()) && count($bundles) == 1) {
-        $type .= ':' . reset($bundles);
+        $bundle = reset($bundles);
+        if ($bundle != $entity_type) {
+          $type .= ':' . $bundle;
+        }
       }
     }
     return $type;
diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php
index 03fab78..26e885e 100644
--- a/core/lib/Drupal/Core/Field/FieldItemInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php
@@ -90,7 +90,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
   /**
    * Gets the entity that field belongs to.
    *
-   * @return \Drupal\Core\Entity\EntityInterface
+   * @return \Drupal\Core\Entity\ContentEntityInterface
    *   The entity object.
    */
   public function getEntity();
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index 69daf67..f6ae7d5 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -57,7 +57,9 @@ public function __construct(DataDefinitionInterface $definition, $name = NULL, T
    * {@inheritdoc}
    */
   public function getEntity() {
-    return $this->getParent();
+    // The "parent" is the TypedData object for the entity, we need to unwrap
+    // the actual entity.
+    return $this->getParent()->getValue();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Plugin/Context/Context.php b/core/lib/Drupal/Core/Plugin/Context/Context.php
index 0366b71..a0ad01f 100644
--- a/core/lib/Drupal/Core/Plugin/Context/Context.php
+++ b/core/lib/Drupal/Core/Plugin/Context/Context.php
@@ -10,7 +10,6 @@
 use Drupal\Component\Plugin\Context\Context as ComponentContext;
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Utility\String;
-use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\Core\TypedData\TypedDataTrait;
 
@@ -47,11 +46,6 @@ public function getContextValue() {
       }
       return NULL;
     }
-    // Special case entities.
-    // @todo: Remove once entities do not implemented TypedDataInterface.
-    if ($this->contextData instanceof ContentEntityInterface) {
-      return $this->contextData;
-    }
     return $this->contextData->getValue();
   }
 
diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
index 923090e..1397485 100644
--- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
+++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
@@ -31,11 +31,13 @@
    * @param $property_name
    *   The name of the property to get; e.g., 'title' or 'name'.
    *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The property object.
+   *
    * @throws \InvalidArgumentException
    *   If an invalid property name is given.
-   *
-   * @return \Drupal\Core\TypedData\TypedDataInterface
-   *   The property object.
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no property can be created.
    */
   public function get($property_name);
 
@@ -51,11 +53,13 @@ public function get($property_name);
    *   TRUE. If the update stems from a parent object, set it to FALSE to avoid
    *   being notified again.
    *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The property object.
+   *
    * @throws \InvalidArgumentException
    *   If the specified property does not exist.
-   *
-   * @return \Drupal\Core\TypedData\TypedDataInterface
-   *   The property object.
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no property can be set.
    */
   public function set($property_name, $value, $notify = TRUE);
 
@@ -68,6 +72,9 @@ public function set($property_name, $value, $notify = TRUE);
    * @return \Drupal\Core\TypedData\TypedDataInterface[]
    *   An array of property objects implementing the TypedDataInterface, keyed
    *   by property name.
+   *
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no property can be created.
    */
   public function getProperties($include_computed = FALSE);
 
@@ -79,6 +86,9 @@ public function getProperties($include_computed = FALSE);
    *
    * @return array
    *   An array of property values, keyed by property name.
+   *
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no property can be created.
    */
   public function toArray();
 
diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionException.php b/core/lib/Drupal/Core/TypedData/DataDefinitionException.php
deleted file mode 100644
index 2a23315..0000000
--- a/core/lib/Drupal/Core/TypedData/DataDefinitionException.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\TypedData\MissingContextException.
- */
-
-namespace Drupal\Core\TypedData;
-
-/**
- * Exception thrown when a data definition lacks required information.
- */
-class DataDefinitionException extends \Exception {
-
-}
diff --git a/core/lib/Drupal/Core/TypedData/Exception/MissingDataException.php b/core/lib/Drupal/Core/TypedData/Exception/MissingDataException.php
new file mode 100644
index 0000000..e937212
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/Exception/MissingDataException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\Exception\MissingDataException.
+ */
+
+namespace Drupal\Core\TypedData\Exception;
+
+/**
+ * Exception thrown when an operation misses a data value; i.e., it is unset.
+ */
+class MissingDataException extends \Exception {}
diff --git a/core/lib/Drupal/Core/TypedData/ReadOnlyException.php b/core/lib/Drupal/Core/TypedData/Exception/ReadOnlyException.php
similarity index 57%
rename from core/lib/Drupal/Core/TypedData/ReadOnlyException.php
rename to core/lib/Drupal/Core/TypedData/Exception/ReadOnlyException.php
index 2486a16..ffd4296 100644
--- a/core/lib/Drupal/Core/TypedData/ReadOnlyException.php
+++ b/core/lib/Drupal/Core/TypedData/Exception/ReadOnlyException.php
@@ -2,10 +2,10 @@
 
 /**
  * @file
- * Contains \Drupal\Core\TypedData\ReadOnlyException.
+ * Contains \Drupal\Core\TypedData\Exception\ReadOnlyException.
  */
 
-namespace Drupal\Core\TypedData;
+namespace Drupal\Core\TypedData\Exception;
 
 /**
  * Exception thrown when trying to write or set ready-only data.
diff --git a/core/lib/Drupal/Core/TypedData/ListInterface.php b/core/lib/Drupal/Core/TypedData/ListInterface.php
index bf70d6f..ce59dfb 100644
--- a/core/lib/Drupal/Core/TypedData/ListInterface.php
+++ b/core/lib/Drupal/Core/TypedData/ListInterface.php
@@ -57,6 +57,9 @@ public function onChange($delta);
    * @return \Drupal\Core\TypedData\TypedDataInterface
    *   The item at the specified position in this list. An empty item is created
    *   if it does not exist yet.
+   *
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no item can be created.
    */
   public function get($index);
 
@@ -70,6 +73,9 @@ public function get($index);
    *
    * @return static
    *   Returns the list.
+   *
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no item can be set.
    */
   public function set($index, $item);
 
@@ -78,6 +84,9 @@ public function set($index, $item);
    *
    * @return \Drupal\Core\TypedData\TypedDataInterface
    *   The first item in this list.
+   *
+   * @throws \Drupal\Core\TypedData\Exception\MissingDataException
+   *   If the complex data structure is unset and no item can be created.
    */
   public function first();
 
diff --git a/core/lib/Drupal/Core/TypedData/MissingContextException.php b/core/lib/Drupal/Core/TypedData/MissingContextException.php
deleted file mode 100644
index b4a7c8e..0000000
--- a/core/lib/Drupal/Core/TypedData/MissingContextException.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\TypedData\MissingContextException.
- */
-
-namespace Drupal\Core\TypedData;
-
-/**
- * Exception thrown when data wrappers miss contextual information.
- */
-class MissingContextException extends \Exception {}
diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
index 664bf3d..b3b832b 100644
--- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
+++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
@@ -44,7 +44,7 @@ public function getTranslationLanguages($include_default = TRUE);
    *   LanguageInterface::LANGCODE_DEFAULT
    *   to get the data in default language.
    *
-   * @return \Drupal\Core\TypedData\TypedDataInterface
+   * @return $this
    *   A typed data object for the translated data.
    */
   public function getTranslation($langcode);
@@ -52,7 +52,7 @@ public function getTranslation($langcode);
   /**
    * Returns the translatable object referring to the original language.
    *
-   * @return \Drupal\Core\TypedData\TranslatableInterface
+   * @return $this
    *   The translation object referring to the original language.
    */
   public function getUntranslated();
@@ -77,7 +77,7 @@ public function hasTranslation($langcode);
    *   (optional) An array of initial values to be assigned to the translatable
    *   fields. Defaults to none.
    *
-   * @return \Drupal\Core\TypedData\TranslatableInterface
+   * @return $this
    */
   public function addTranslation($langcode, array $values = array());
 
diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php
index 1ad3fb8..b0f614c 100644
--- a/core/lib/Drupal/Core/TypedData/TypedData.php
+++ b/core/lib/Drupal/Core/TypedData/TypedData.php
@@ -44,6 +44,13 @@
   protected $parent;
 
   /**
+   * {@inheritdoc}
+   */
+  public static function createInstance($definition, $name = NULL, TypedDataInterface $parent = NULL) {
+    return new static($definition, $name, $parent);
+  }
+
+  /**
    * Constructs a TypedData object given its definition and context.
    *
    * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataInterface.php b/core/lib/Drupal/Core/TypedData/TypedDataInterface.php
index a7eb8a2..74d083f 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataInterface.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataInterface.php
@@ -19,6 +19,26 @@
 interface TypedDataInterface {
 
   /**
+   * Constructs a TypedData object given its definition and context.
+   *
+   * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
+   *   The data definition.
+   * @param string|null $name
+   *   (optional) The name of the created property, or NULL if it is the root
+   *   of a typed data tree. Defaults to NULL.
+   * @param \Drupal\Core\TypedData\TypedDataInterface $parent
+   *   (optional) The parent object of the data property, or NULL if it is the
+   *   root of a typed data tree. Defaults to NULL.
+   *
+   * @todo When \Drupal\Core\Config\TypedConfigManager has been fixed to use
+   *   class-based definitions, type-hint $definition to
+   *   DataDefinitionInterface. https://drupal.org/node/1928868
+   *
+   * @see \Drupal\Core\TypedData\TypedDataManager::create()
+   */
+  public static function createInstance($definition, $name = NULL, TypedDataInterface $parent = NULL);
+
+  /**
    * Gets the data definition.
    *
    * @return \Drupal\Core\TypedData\DataDefinitionInterface
@@ -46,7 +66,7 @@ public function getValue();
    *
    * @throws \InvalidArgumentException
    *   If the value input is inappropriate.
-   * @throws \Drupal\Core\TypedData\ReadOnlyException
+   * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
    *   If the data is read-only.
    */
   public function setValue($value, $notify = TRUE);
@@ -136,10 +156,10 @@ public function getPropertyPath();
    *
    * This method is supposed to be called by the factory only.
    *
-   * @param string $name
+   * @param string|null $name
    *   (optional) The name of the property or the delta of the list item,
    *   or NULL if it is the root of a typed data tree. Defaults to NULL.
-   * @param \Drupal\Core\TypedData\TypedDataInterface $parent
+   * @param \Drupal\Core\TypedData\TypedDataInterface|null $parent
    *   (optional) The parent object of the data property, or NULL if it is the
    *   root of a typed data tree. Defaults to NULL.
    */
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 726ae38..99cc306 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -95,7 +95,7 @@ public function createInstance($data_type, array $configuration = array()) {
     if (!isset($class)) {
       throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $data_type));
     }
-    return new $class($data_definition, $configuration['name'], $configuration['parent']);
+    return $class::createInstance($data_definition, $configuration['name'], $configuration['parent']);
   }
 
   /**
diff --git a/core/modules/aggregator/src/Plugin/views/row/Rss.php b/core/modules/aggregator/src/Plugin/views/row/Rss.php
index ed1393e..d699d55 100644
--- a/core/modules/aggregator/src/Plugin/views/row/Rss.php
+++ b/core/modules/aggregator/src/Plugin/views/row/Rss.php
@@ -73,9 +73,9 @@ public function render($row) {
     $entity = $row->_entity;
 
     $item = new \stdClass();
-    foreach ($entity->getProperties() as $name => $value) {
+    foreach ($entity as $name => $field) {
       // views_view_row_rss takes care about the escaping.
-      $item->{$name} = $value->value;
+      $item->{$name} = $field->value;
     }
 
     $item->elements = array(
diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
index cec6b4a..6a24454 100644
--- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
+++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php
@@ -85,7 +85,7 @@ public function normalize($entity, $format = NULL, array $context = array()) {
       }
     }
     else {
-      $fields = $entity->getProperties();
+      $fields = $entity->getFields();
     }
     // Ignore the entity ID and revision ID.
     $exclude = array($entity->getEntityType()->getKey('id'), $entity->getEntityType()->getKey('revision'));
diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php
index c9eaa9a..652c650 100644
--- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php
@@ -61,10 +61,10 @@ public function testNormalize() {
 
     $content_entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
       ->disableOriginalConstructor()
-      ->setMethods(array('getProperties'))
+      ->setMethods(array('getFields'))
       ->getMockForAbstractClass();
     $content_entity->expects($this->once())
-      ->method('getProperties')
+      ->method('getFields')
       ->will($this->returnValue($definitions));
 
     $serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer')
diff --git a/core/modules/system/src/Tests/Entity/EntityFieldTest.php b/core/modules/system/src/Tests/Entity/EntityFieldTest.php
index e9d50ad..dd9b2ef 100644
--- a/core/modules/system/src/Tests/Entity/EntityFieldTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityFieldTest.php
@@ -416,27 +416,28 @@ protected function checkIntrospection($entity_type) {
     $this->assertEqual($textfield_properties['processed']->getDataType(), 'string', $entity_type .': String processed property of the test-text field found.');
 
     // Make sure provided contextual information is right.
-    $this->assertIdentical($entity->getRoot(), $entity, 'Entity is root object.');
-    $this->assertEqual($entity->getPropertyPath(), '');
-    $this->assertEqual($entity->getName(), '');
-    $this->assertEqual($entity->getParent(), NULL);
+    $entity_adapter = $entity->getTypedData();
+    $this->assertIdentical($entity_adapter->getRoot(), $entity_adapter, 'Entity is root object.');
+    $this->assertEqual($entity_adapter->getPropertyPath(), '');
+    $this->assertEqual($entity_adapter->getName(), '');
+    $this->assertEqual($entity_adapter->getParent(), NULL);
 
     $field = $entity->user_id;
-    $this->assertIdentical($field->getRoot(), $entity, 'Entity is root object.');
+    $this->assertIdentical($field->getRoot()->getValue(), $entity, 'Entity is root object.');
     $this->assertIdentical($field->getEntity(), $entity, 'getEntity() returns the entity.');
     $this->assertEqual($field->getPropertyPath(), 'user_id');
     $this->assertEqual($field->getName(), 'user_id');
-    $this->assertIdentical($field->getParent(), $entity, 'Parent object matches.');
+    $this->assertIdentical($field->getParent()->getValue(), $entity, 'Parent object matches.');
 
     $field_item = $field[0];
-    $this->assertIdentical($field_item->getRoot(), $entity, 'Entity is root object.');
+    $this->assertIdentical($field_item->getRoot()->getValue(), $entity, 'Entity is root object.');
     $this->assertIdentical($field_item->getEntity(), $entity, 'getEntity() returns the entity.');
     $this->assertEqual($field_item->getPropertyPath(), 'user_id.0');
     $this->assertEqual($field_item->getName(), '0');
     $this->assertIdentical($field_item->getParent(), $field, 'Parent object matches.');
 
     $item_value = $field_item->get('entity');
-    $this->assertIdentical($item_value->getRoot(), $entity, 'Entity is root object.');
+    $this->assertIdentical($item_value->getRoot()->getValue(), $entity, 'Entity is root object.');
     $this->assertEqual($item_value->getPropertyPath(), 'user_id.0.entity');
     $this->assertEqual($item_value->getName(), 'entity');
     $this->assertIdentical($item_value->getParent(), $field_item, 'Parent object matches.');
@@ -476,9 +477,9 @@ protected function assertIterator($entity_type) {
       }
     }
 
-    $properties = $entity->getProperties();
-    $this->assertEqual(array_keys($properties), array_keys($entity->getDataDefinition()->getPropertyDefinitions()), format_string('%entity_type: All properties returned.', array('%entity_type' => $entity_type)));
-    $this->assertEqual($properties, iterator_to_array($entity->getIterator()), format_string('%entity_type: Entity iterator iterates over all properties.', array('%entity_type' => $entity_type)));
+    $fields = $entity->getFields();
+    $this->assertEqual(array_keys($fields), array_keys($entity->getTypedData()->getDataDefinition()->getPropertyDefinitions()), format_string('%entity_type: All fields returned.', array('%entity_type' => $entity_type)));
+    $this->assertEqual($fields, iterator_to_array($entity->getIterator()), format_string('%entity_type: Entity iterator iterates over all fields.', array('%entity_type' => $entity_type)));
   }
 
   /**
@@ -504,7 +505,7 @@ protected function assertDataStructureInterfaces($entity_type) {
     // contained properties and getting all contained strings, limited by a
     // certain depth.
     $strings = array();
-    $this->getContainedStrings($entity, 0, $strings);
+    $this->getContainedStrings($entity->getTypedData(), 0, $strings);
 
     // @todo: Once the user entity has defined properties this should contain
     // the user name and other user entity strings as well.
diff --git a/core/modules/system/src/Tests/Entity/EntityUUIDTest.php b/core/modules/system/src/Tests/Entity/EntityUUIDTest.php
index 39bfa70..5f3b6e1 100644
--- a/core/modules/system/src/Tests/Entity/EntityUUIDTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityUUIDTest.php
@@ -73,7 +73,7 @@ protected function assertCRUD($entity_type) {
 
     // Creating a duplicate needs to result in a new UUID.
     $entity_duplicate = $entity->createDuplicate();
-    foreach ($entity->getProperties() as $property => $value) {
+    foreach ($entity->getFields() as $property => $value) {
       switch($property) {
         case 'uuid':
           $this->assertNotNull($entity_duplicate->uuid());
diff --git a/core/modules/system/src/Tests/Entity/EntityValidationTest.php b/core/modules/system/src/Tests/Entity/EntityValidationTest.php
index 73f0fd9..8fdcf82 100644
--- a/core/modules/system/src/Tests/Entity/EntityValidationTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityValidationTest.php
@@ -133,7 +133,7 @@ protected function checkValidation($entity_type) {
 
     // Make sure the information provided by a violation is correct.
     $violation = $violations[0];
-    $this->assertEqual($violation->getRoot(), $test_entity, 'Violation root is entity.');
+    $this->assertEqual($violation->getRoot()->getValue(), $test_entity, 'Violation root is entity.');
     $this->assertEqual($violation->getPropertyPath(), 'name.0.value', 'Violation property path is correct.');
     $this->assertEqual($violation->getInvalidValue(), $test_entity->name->value, 'Violation contains invalid value.');
 
@@ -151,7 +151,7 @@ protected function checkValidation($entity_type) {
 
     // Make sure the information provided by a violation is correct.
     $violation = $violations[0];
-    $this->assertEqual($violation->getRoot(), $test_entity, 'Violation root is entity.');
+    $this->assertEqual($violation->getRoot()->getValue(), $test_entity, 'Violation root is entity.');
     $this->assertEqual($violation->getPropertyPath(), 'field_test_text.0.format', 'Violation property path is correct.');
     $this->assertEqual($violation->getInvalidValue(), $test_entity->field_test_text->format, 'Violation contains invalid value.');
   }
diff --git a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
index d67d055..c7dab96 100644
--- a/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
+++ b/core/modules/system/src/Tests/Plugin/ContextPluginTest.php
@@ -68,7 +68,7 @@ function testContext() {
     $user = entity_create('user', array('name' => $name));
     $plugin->setContextValue('user', $user);
 
-    $this->assertEqual($plugin->getContextValue('user')->getName(), $user->getName());
+    $this->assertEqual($plugin->getContextValue('user')->getUsername(), $user->getUsername());
     $this->assertEqual($user->label(), $plugin->getTitle());
 
     // Test Optional context handling.
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index bf6b42a..f81f251 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -1127,4 +1127,11 @@ public function getListCacheTags() {
     $this->storage->getListCacheTags();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypedData() {
+    $this->storage->getTypedData();
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php
index b543ec3..168f86e 100644
--- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Core\Language\Language;
@@ -138,6 +137,10 @@ protected function setUp() {
     $this->typedDataManager = $this->getMockBuilder('\Drupal\Core\TypedData\TypedDataManager')
       ->disableOriginalConstructor()
       ->getMock();
+    $this->typedDataManager->expects($this->any())
+      ->method('getDefinition')
+      ->with('entity')
+      ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\Entity']));
 
     $english = new Language(array('id' => 'en'));
     $not_specified = new Language(array('id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE));
@@ -228,7 +231,7 @@ public function testIsNewRevision() {
 
     $this->typedDataManager->expects($this->any())
       ->method('getPropertyInstance')
-      ->with($this->entity, 'revision_id', NULL)
+      ->with($this->entity->getTypedData(), 'revision_id', NULL)
       ->will($this->returnValue($field_item_list));
 
     $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item));
@@ -305,23 +308,6 @@ public function testPreSaveRevision() {
   }
 
   /**
-   * @covers ::getString
-   */
-  public function testGetString() {
-    $label = $this->randomMachineName();
-    /** @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject $entity */
-    $entity = $this->getMockBuilder('\Drupal\Core\Entity\ContentEntityBase')
-      ->setMethods(array('label'))
-      ->disableOriginalConstructor()
-      ->getMockForAbstractClass();
-    $entity->expects($this->once())
-      ->method('label')
-      ->will($this->returnValue($label));
-
-    $this->assertSame($label, $entity->getString());
-  }
-
-  /**
    * @covers ::validate
    */
   public function testValidate() {
@@ -335,11 +321,11 @@ public function testValidate() {
     $non_empty_violation_list->add($violation);
     $validator->expects($this->at(0))
       ->method('validate')
-      ->with($this->entity)
+      ->with($this->entity->getTypedData())
       ->will($this->returnValue($empty_violation_list));
     $validator->expects($this->at(1))
       ->method('validate')
-      ->with($this->entity)
+      ->with($this->entity->getTypedData())
       ->will($this->returnValue($non_empty_violation_list));
     $this->typedDataManager->expects($this->exactly(2))
       ->method('getValidator')
@@ -349,51 +335,6 @@ public function testValidate() {
   }
 
   /**
-   * @covers ::getConstraints
-   */
-  public function testGetConstraints() {
-    $this->assertInternalType('array', $this->entity->getConstraints());
-  }
-
-  /**
-   * @covers ::getName
-   */
-  public function testGetName() {
-    $this->assertNull($this->entity->getName());
-  }
-
-  /**
-   * @covers ::getRoot
-   */
-  public function testGetRoot() {
-    $this->assertSame(spl_object_hash($this->entity), spl_object_hash($this->entity->getRoot()));
-  }
-
-  /**
-   * @covers ::getPropertyPath
-   */
-  public function testGetPropertyPath() {
-    $this->assertSame('', $this->entity->getPropertyPath());
-  }
-
-  /**
-   * @covers ::getParent
-   */
-  public function testGetParent() {
-    $this->assertNull($this->entity->getParent());
-  }
-
-  /**
-   * @covers ::setContext
-   */
-  public function testSetContext() {
-    $name = $this->randomMachineName();
-    $parent = $this->getMock('\Drupal\Core\TypedData\TypedDataInterface');
-    // Our mocked entity->setContext() returns NULL, so assert that.
-    $this->assertNull($this->entity->setContext($name, $parent));
-  }
-
-  /**
    * @covers ::bundle
    */
   public function testBundle() {
diff --git a/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php
new file mode 100644
index 0000000..74b9b44
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php
@@ -0,0 +1,422 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Entity\EntityAdapterUnitTest.
+ */
+
+namespace Drupal\Tests\Core\Entity\TypedData;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Language\Language;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Entity\Plugin\DataType\EntityAdapter
+ * @group Entity
+ * @group TypedData
+ */
+class EntityAdapterUnitTest extends UnitTestCase {
+
+  /**
+   * The bundle used for testing.
+   *
+   * @var string
+   */
+  protected $bundle;
+
+  /**
+   * The entity used for testing.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entity;
+
+  /**
+   * The entity adapter under test.
+   *
+   * @var \Drupal\Core\Entity\Plugin\DataType\EntityAdapter
+   */
+  protected $entityAdapter;
+
+  /**
+   * The entity type used for testing.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityType;
+
+  /**
+   * The entity manager used for testing.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The type ID of the entity under test.
+   *
+   * @var string
+   */
+  protected $entityTypeId;
+
+  /**
+   * The typed data manager used for testing.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $typedDataManager;
+
+  /**
+   * The field item list returned by the typed data manager.
+   *
+   * @var \Drupal\Core\Field\FieldItemListInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $fieldItemList;
+
+  /**
+   * The field type manager used for testing.
+   *
+   * @var \Drupal\Core\Field\FieldTypePluginManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $fieldTypePluginManager;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  /**
+   * The UUID generator used for testing.
+   *
+   * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $uuid;
+
+  /**
+   * The entity ID.
+   *
+   * @var int
+   */
+  protected $id;
+
+  /**
+   * Field definitions.
+   *
+   * @var \Drupal\Core\Field\BaseFieldDefinition[]
+   */
+  protected $fieldDefinitions;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->id = 1;
+    $values = array(
+      'id' => $this->id,
+      'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
+      'defaultLangcode' => array(LanguageInterface::LANGCODE_DEFAULT => 'en'),
+    );
+    $this->entityTypeId = $this->randomMachineName();
+    $this->bundle = $this->randomMachineName();
+
+    $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
+    $this->entityType->expects($this->any())
+      ->method('getKeys')
+      ->will($this->returnValue(array(
+        'id' => 'id',
+        'uuid' => 'uuid',
+    )));
+
+    $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
+    $this->entityManager->expects($this->any())
+      ->method('getDefinition')
+      ->with($this->entityTypeId)
+      ->will($this->returnValue($this->entityType));
+
+    $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
+
+    $this->typedDataManager = $this->getMockBuilder('\Drupal\Core\TypedData\TypedDataManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->typedDataManager->expects($this->any())
+      ->method('getDefinition')
+      ->with('entity')
+      ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\Entity']));
+    $this->typedDataManager->expects($this->any())
+      ->method('getDefaultConstraints')
+      ->willReturn([]);
+
+    $validation_constraint_manager = $this->getMockBuilder('\Drupal\Core\Validation\ConstraintManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $validation_constraint_manager->expects($this->any())
+      ->method('create')
+      ->willReturn([]);
+    $this->typedDataManager->expects($this->any())
+      ->method('getValidationConstraintManager')
+      ->willReturn($validation_constraint_manager);
+
+    $this->fieldItemList = $this->getMock('\Drupal\Core\Field\FieldItemListInterface');
+    $this->typedDataManager->expects($this->any())
+      ->method('getPropertyInstance')
+      ->willReturn($this->fieldItemList);
+
+    $not_specified = new Language(array('id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE));
+    $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
+    $this->languageManager->expects($this->any())
+      ->method('getLanguages')
+      ->will($this->returnValue(array(LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified)));
+
+    $this->languageManager->expects($this->any())
+      ->method('getLanguage')
+      ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
+      ->will($this->returnValue($not_specified));
+
+    $this->fieldTypePluginManager = $this->getMockBuilder('\Drupal\Core\Field\FieldTypePluginManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->fieldTypePluginManager->expects($this->any())
+      ->method('getDefaultStorageSettings')
+      ->will($this->returnValue(array()));
+    $this->fieldTypePluginManager->expects($this->any())
+      ->method('getDefaultFieldSettings')
+      ->will($this->returnValue(array()));
+
+    $container = new ContainerBuilder();
+    $container->set('entity.manager', $this->entityManager);
+    $container->set('uuid', $this->uuid);
+    $container->set('typed_data_manager', $this->typedDataManager);
+    $container->set('language_manager', $this->languageManager);
+    $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
+    \Drupal::setContainer($container);
+
+    $this->fieldDefinitions = array(
+      'id' => BaseFieldDefinition::create('integer'),
+      'revision_id' => BaseFieldDefinition::create('integer'),
+    );
+
+    $this->entityManager->expects($this->any())
+      ->method('getFieldDefinitions')
+      ->with($this->entityTypeId, $this->bundle)
+      ->will($this->returnValue($this->fieldDefinitions));
+    $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', array($values, $this->entityTypeId, $this->bundle));
+
+    $this->entityAdapter = EntityAdapter::createFromEntity($this->entity);
+  }
+
+  /**
+   * @covers ::getConstraints
+   */
+  public function testGetConstraints() {
+    $this->assertInternalType('array', $this->entityAdapter->getConstraints());
+  }
+
+  /**
+   * @covers ::getName
+   */
+  public function testGetName() {
+    $this->assertNull($this->entityAdapter->getName());
+  }
+
+  /**
+   * @covers ::getRoot
+   */
+  public function testGetRoot() {
+    $this->assertSame(spl_object_hash($this->entityAdapter), spl_object_hash($this->entityAdapter->getRoot()));
+  }
+
+  /**
+   * @covers ::getPropertyPath
+   */
+  public function testGetPropertyPath() {
+    $this->assertSame('', $this->entityAdapter->getPropertyPath());
+  }
+
+  /**
+   * @covers ::getParent
+   */
+  public function testGetParent() {
+    $this->assertNull($this->entityAdapter->getParent());
+  }
+
+  /**
+   * @covers ::setContext
+   */
+  public function testSetContext() {
+    $name = $this->randomMachineName();
+    $parent = $this->getMock('\Drupal\Core\TypedData\TypedDataInterface');
+    // Our mocked entity->setContext() returns NULL, so assert that.
+    $this->assertNull($this->entityAdapter->setContext($name, $parent));
+    $this->assertEquals($name, $this->entityAdapter->getName());
+    $this->assertEquals($parent, $this->entityAdapter->getParent());
+  }
+
+  /**
+   * @covers ::getValue
+   */
+  public function testGetValue() {
+    $this->assertEquals($this->entity, $this->entityAdapter->getValue());
+  }
+
+  /**
+   * @covers ::setValue
+   */
+  public function testSetValue() {
+    $this->entityAdapter->setValue(NULL);
+    $this->assertNull($this->entityAdapter->getValue());
+  }
+
+  /**
+   * @covers ::get
+   */
+  public function testGet() {
+    $this->assertInstanceOf('\Drupal\Core\Field\FieldItemListInterface', $this->entityAdapter->get('id'));
+  }
+
+  /**
+   * @covers ::get
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetInvalidField() {
+    $this->entityAdapter->get('invalid');
+  }
+
+  /**
+   * @covers ::get
+   * @expectedException \Drupal\Core\TypedData\Exception\MissingDataException
+   */
+  public function testGetWithoutData() {
+    $this->entityAdapter->setValue(NULL);
+    $this->entityAdapter->get('id');
+  }
+
+  /**
+   * @covers ::set
+   */
+  public function testSet() {
+    $id_items = [ array('value' => $this->id + 1) ];
+
+    $this->fieldItemList->expects($this->once())
+      ->method('setValue')
+      ->with($id_items);
+
+    $this->entityAdapter->set('id', $id_items);
+  }
+
+  /**
+   * @covers ::set
+   * @expectedException \Drupal\Core\TypedData\Exception\MissingDataException
+   */
+  public function testSetWithoutData() {
+    $this->entityAdapter->setValue(NULL);
+    $id_items = [ array('value' => $this->id + 1) ];
+    $this->entityAdapter->set('id', $id_items);
+  }
+
+  /**
+ * @covers ::getProperties
+ */
+  public function testGetProperties() {
+    $fields = $this->entityAdapter->getProperties();
+    $this->assertInstanceOf('Drupal\Core\Field\FieldItemListInterface', $fields['id']);
+    $this->assertInstanceOf('Drupal\Core\Field\FieldItemListInterface', $fields['revision_id']);
+  }
+
+  /**
+   * @covers ::toArray
+   */
+  public function testToArray() {
+    $array = $this->entityAdapter->toArray();
+    // Mock field objects return NULL values, so test keys only.
+    $this->assertArrayHasKey('id', $array);
+    $this->assertArrayHasKey('revision_id', $array);
+    $this->assertEquals(count($array), 2);
+  }
+
+  /**
+   * @covers ::toArray
+   * @expectedException \Drupal\Core\TypedData\Exception\MissingDataException
+   */
+  public function testToArrayWithoutData() {
+    $this->entityAdapter->setValue(NULL);
+    $this->entityAdapter->toArray();
+  }
+
+  /**
+   * @covers ::isEmpty
+   */
+  public function testIsEmpty() {
+    $this->assertFalse($this->entityAdapter->isEmpty());
+    $this->entityAdapter->setValue(NULL);
+    $this->assertTrue($this->entityAdapter->isEmpty());
+  }
+
+  /**
+   * @covers ::onChange
+   */
+  public function testOnChange() {
+    $entity = $this->getMock('\Drupal\Core\Entity\ContentEntityInterface');
+    $entity->expects($this->once())
+      ->method('onChange')
+      ->with('foo')
+      ->willReturn(NULL);
+    $this->entityAdapter->setValue($entity);
+    $this->entityAdapter->onChange('foo');
+  }
+
+  /**
+   * @covers ::getDataDefinition
+   */
+  public function testGetDataDefinition() {
+    $definition = $this->entityAdapter->getDataDefinition();
+    $this->assertInstanceOf('\Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface', $definition);
+    $this->assertEquals($definition->getEntityTypeId(), $this->entityTypeId);
+    $this->assertEquals($definition->getBundles(), [ $this->bundle ]);
+  }
+
+  /**
+   * @covers ::getString
+   */
+  public function testGetString() {
+    $entity = $this->getMock('\Drupal\Core\Entity\ContentEntityInterface');
+    $entity->expects($this->once())
+      ->method('label')
+      ->willReturn('foo');
+    $this->entityAdapter->setValue($entity);
+    $this->assertEquals('foo', $this->entityAdapter->getString());
+    $this->entityAdapter->setValue(NULL);
+    $this->assertEquals('', $this->entityAdapter->getString());
+  }
+
+  /**
+   * @covers ::applyDefaultValue
+   */
+  public function testApplyDefaultValue() {
+    // For each field on the entity the mock method has to be invoked once.
+    $this->fieldItemList->expects($this->exactly(2))
+      ->method('applyDefaultValue');
+    $this->entityAdapter->applyDefaultValue();
+  }
+
+  /**
+   * @covers ::getIterator
+   */
+  public function testGetIterator() {
+    $iterator = $this->entityAdapter->getIterator();
+    $fields = iterator_to_array($iterator);
+    $this->assertArrayHasKey('id', $fields);
+    $this->assertArrayHasKey('revision_id', $fields);
+    $this->assertEquals(count($fields), 2);
+
+    $this->entityAdapter->setValue(NULL);
+    $this->assertEquals(new \ArrayIterator([]), $this->entityAdapter->getIterator());
+  }
+}
