diff --git a/core/includes/common.inc b/core/includes/common.inc
index 17e1363..9450822 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -7316,6 +7316,53 @@ function drupal_get_updaters() {
 }
 
 /**
+ * Gets information about all data types.
+ *
+ * @see drupal_get_property()
+ */
+function drupal_get_data_type_info() {
+  $items = &drupal_static('data_type_info');
+  if (!isset($items)) {
+    $items = module_invoke_all('data_type_info');
+    drupal_alter('data_type_info', $items);
+  }
+  return $items;
+}
+
+/**
+ * Gets a property object.
+ *
+ * @param array $definition
+ *   The property's definition.
+ * @param $value
+ *   (optional) The property's value. If set, it has to match the property
+ *   type's format.
+ * @param $context
+ *   (optional) An array describing the context of the property. It should be
+ *   passed if a property is created as part of a property container. The
+ *   following keys are supported:
+ *   - name: The name of the property being created.
+ *   - parent: The parent object containing the property. Must be an instance of
+ *     \Drupal\Core\Data\DataStructureInterface.
+ *
+ * @return \Drupal\Core\Data\DataItemInterface
+ *
+ * @see drupal_get_data_type_info()
+ */
+function drupal_get_property(array $definition, $value = NULL, $context = array()) {
+  $type_info = &drupal_static('data_type_info');
+
+  if (!isset($type_info)) {
+    drupal_get_data_type_info();
+  }
+
+  // Allow per-property definition overrides of the used classes.
+  $class_info = $definition + $type_info[$definition['type']];
+  $class = empty($definition['list']) ? $class_info['class'] : $class_info['list class'];
+  return new $class($definition, $value, $context);
+}
+
+/**
  * Assembles the Drupal FileTransfer registry.
  *
  * @return
diff --git a/core/lib/Drupal/Core/Data/DataItemInterface.php b/core/lib/Drupal/Core/Data/DataItemInterface.php
new file mode 100644
index 0000000..1e8c059
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/DataItemInterface.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Data\DataItemInterface.
+ */
+
+namespace Drupal\Core\Data;
+
+/**
+ * Interface for all properties.
+ */
+interface DataItemInterface {
+
+  /**
+   * Creates a property object given its definition.
+   *
+   * @param array $definition
+   *   The definition of the property.
+   * @param mixed $value
+   *   (optional) The value of the property, or NULL if the property is not set.
+   *    See DataItemInterface::setValue() for details.
+   * @param $context
+   *   (optional) An array describing the context of the property. It should be
+   *   passed if a property is created as part of a property container. The
+   *   following keys are supported:
+   *   - name: The name of the property being created.
+   *   - parent: The parent object containing the property. Must be an instance of
+   *     \Drupal\Core\Data\DataStructureInterface.
+   *
+   * @see drupal_get_property()
+   */
+  public function __construct(array $definition, $value = NULL, $context = array());
+
+  /**
+   * Gets the data type of the property.
+   *
+   * @return string
+   *   The data type of the property.
+   */
+  public function getType();
+
+  /**
+   * Gets the definition of the property.
+   *
+   * @return array
+   *   The definition of the property.
+   */
+  public function getDefinition();
+
+  /**
+   * Gets the value of the property.
+   *
+   * @return mixed
+   */
+  public function getValue();
+
+  /**
+   * Sets the value of the property.
+   *
+   * @param mixed $value
+   *   The value to set in the format as documented for the property's type or
+   *   NULL to unset the property.
+   *
+   * @throws \Drupal\Core\Data\DataReadOnlyException
+   *   If the property is read-only.
+   */
+  public function setValue($value);
+
+  /**
+   * Returns a string representation of the property.
+   *
+   * @return string
+   */
+  public function getString();
+
+  /**
+   * Validates the property value.
+   *
+   * @param mixed $value
+   *   (optional) If specified, the given value is validated. Otherwise the
+   *   currently set value is validated.
+   */
+  public function validate($value = NULL);
+}
diff --git a/core/lib/Drupal/Core/Data/DataListInterface.php b/core/lib/Drupal/Core/Data/DataListInterface.php
new file mode 100644
index 0000000..3e4c957
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/DataListInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Data\DataListInterface.
+ */
+
+namespace Drupal\Core\Data;
+use IteratorAggregate;
+use ArrayAccess;
+use Countable;
+
+/**
+ * Interface for a list of properties.
+ */
+interface DataListInterface extends DataItemInterface, ArrayAccess, IteratorAggregate, Countable { }
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Data/DataReadOnlyException.php b/core/lib/Drupal/Core/Data/DataReadOnlyException.php
new file mode 100644
index 0000000..248cfa7
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/DataReadOnlyException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Data\DataReadOnlyException.
+ */
+
+namespace Drupal\Core\Data;
+
+/**
+ * Exception thrown when trying to write or set a ready-only property.
+ */
+class DataReadOnlyException extends \Exception { }
diff --git a/core/lib/Drupal/Core/Data/DataStructureInterface.php b/core/lib/Drupal/Core/Data/DataStructureInterface.php
new file mode 100644
index 0000000..054b110
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/DataStructureInterface.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Data\DataStructureInterface.
+ */
+
+namespace Drupal\Core\Data;
+use IteratorAggregate;
+
+/**
+ * Interface for data structures that contain properties.
+ *
+ * This is implemented by entities as well as by PropertyItem classes.
+ */
+interface DataStructureInterface extends IteratorAggregate  {
+
+  /**
+   * Gets an array of properties.
+   *
+   * The array contains all properties which are not computed.
+   * @todo: How to get computed properties via the interface?
+   *
+   * @return array
+   *   An array of property objects implementing the DataItemInterface, keyed
+   *   by property name.
+   */
+  public function getProperties();
+
+  /**
+   * Sets an array of properties.
+   *
+   * @param array
+   *   The array of properties to set. The array has to consist of property
+   *   values or property objects implementing the DataItemInterface and must be
+   *   keyed by property name.
+   *
+   * @throws \InvalidArgumentException
+   *   If an not existing property is passed.
+   * @throws \Drupal\Core\Data\DataReadOnlyException
+   *   If a read-only property is set.
+   */
+  public function setProperties($properties);
+
+  /**
+   * Gets the definition of a contained property.
+   *
+   * @param string $name
+   *   The name of property.
+   *
+   * @return array|FALSE
+   *   The definition of the property or FALSE if the property does not exist.
+   */
+  public function getPropertyDefinition($name);
+
+  /**
+   * Gets an array property definitions of contained properties.
+   *
+   * @param array $definition
+   *   The definition of the container's property, e.g. the definition of an
+   *   entity reference property.
+   *
+   * @return array
+   *   An array of property definitions of contained properties, keyed by
+   *   property name.
+   */
+  public function getPropertyDefinitions();
+
+  /**
+   * Gets the the raw array representation of the contained properties.
+   *
+   * @return array
+   *   The raw array representation of the contained properties, i.e. an array
+   *   keyed by property name containing the raw values.
+   */
+  public function toArray();
+
+}
diff --git a/core/lib/Drupal/Core/Data/Type/Integer.php b/core/lib/Drupal/Core/Data/Type/Integer.php
new file mode 100644
index 0000000..df9ce1c
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/Type/Integer.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Data\Type\Integer.
+ */
+
+namespace Drupal\Core\Data\Type;
+use Drupal\Core\Data\DataItemInterface;
+
+/**
+ * The string property type.
+ */
+class Integer implements DataItemInterface {
+
+  /**
+   * The property definition.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * The property value.
+   *
+   * @var integer
+   */
+  protected $value;
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+    if (isset($value)) {
+      $this->setValue($value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    return $this->value;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   */
+  public function setValue($value) {
+    $this->value = $value;
+  }
+
+  /**
+   * Implements DataItemInterface::getString().
+   */
+  public function getString() {
+    return (string) $this->value;
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // TODO: Implement validate() method.
+  }
+}
diff --git a/core/lib/Drupal/Core/Data/Type/Language.php b/core/lib/Drupal/Core/Data/Type/Language.php
new file mode 100644
index 0000000..659e6dd
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/Type/Language.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Data\Type\Language.
+ */
+
+namespace Drupal\Core\Data\Type;
+use \Drupal\Core\Data\DataItemInterface;
+
+/**
+ * Defines the 'language' property type, e.g. the computed 'language' property of language items.
+ */
+class Language implements DataItemInterface {
+
+  /**
+   * The property definition.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * The property holding the langcode.
+   *
+   * @var \Drupal\Core\Data\DataItemInterface
+   */
+  protected $langcode;
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+
+    if (isset($context['parent'])) {
+      $this->langcode = $context['parent']->get('langcode');
+    }
+    else {
+      // No context given, so just initialize an langcode property for storing
+      // the code.
+      $this->langcode = drupal_get_property(array('type' => 'string'));
+    }
+
+    if (isset($value)) {
+      $this->setValue($value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    $langcode = $this->langcode->getValue();
+    return $langcode ? language_load($langcode) : NULL;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   *
+   * Both the langcode and the language object may be passed as value.
+   */
+  public function setValue($value) {
+    if (!isset($value)) {
+      $this->langcode->setValue(NULL);
+    }
+    elseif (is_scalar($value)) {
+      $this->langcode->setValue($value);
+    }
+    elseif (is_object($value)) {
+      $this->langcode->setValue($value->langcode);
+    }
+    else {
+      throw new \InvalidArgumentException('Value is no valid langcode or language object.');
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getString().
+   */
+  public function getString() {
+    $language = $this->getValue();
+    return $language ? $language->name : '';
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // TODO: Implement validate() method.
+  }
+}
diff --git a/core/lib/Drupal/Core/Data/Type/String.php b/core/lib/Drupal/Core/Data/Type/String.php
new file mode 100644
index 0000000..c86c637
--- /dev/null
+++ b/core/lib/Drupal/Core/Data/Type/String.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Data\Type\String.
+ */
+
+namespace Drupal\Core\Data\Type;
+use Drupal\Core\Data\DataItemInterface;
+
+/**
+ * The string property type.
+ */
+class String implements DataItemInterface {
+
+  /**
+   * The property definition.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * The property value.
+   *
+   * @var string
+   */
+  protected $value;
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+    if (isset($value)) {
+      $this->setValue($value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    return $this->value;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   */
+  public function setValue($value) {
+    $this->value = $value;
+  }
+
+  /**
+   * Implements DataItemInterface::getString().
+   */
+  public function getString() {
+    return (string) $this->value;
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // TODO: Implement validate() method.
+  }
+}
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index 3d37a2a..f2a411c 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -474,3 +474,40 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
     field_attach_submit($entity_type, $entity, $form, $form_state);
   }
 }
+
+/**
+ * Implements hook_data_type_info().
+ */
+function entity_data_type_info() {
+  return array(
+    'string_item' => array(
+      'label' => t('String item'),
+      'description' => t('An entity property containing a string value.'),
+      'class' => '\Drupal\entity\Property\PropertyStringItem',
+      'list class' => '\Drupal\entity\Property\EntityPropertyList',
+    ),
+    'integer_item' => array(
+      'label' => t('Integer item'),
+      'description' => t('An entity property containing an integer value.'),
+      'class' => '\Drupal\entity\Property\PropertyIntegerItem',
+      'list class' => '\Drupal\entity\Property\EntityPropertyList',
+    ),
+    'language_item' => array(
+      'label' => t('Language item'),
+      'description' => t('An entity property referencing a language.'),
+      'class' => '\Drupal\entity\Property\PropertyLanguageItem',
+      'list class' => '\Drupal\entity\Property\EntityPropertyList',
+    ),
+    'entityreference_item' => array(
+      'label' => t('Entity reference item'),
+      'description' => t('An entity property containing an entity reference.'),
+      'class' => '\Drupal\entity\Property\PropertyEntityReferenceItem',
+      'list class' => '\Drupal\entity\Property\EntityPropertyList',
+    ),
+    'entity' => array(
+      'label' => t('Entity'),
+      'description' => t('All kind of entities, e.g. nodes, comments or users.'),
+      'class' => '\Drupal\entity\Property\PropertyEntity',
+    ),
+  );
+}
diff --git a/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php b/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php
index 8c4f3c8..79ad256 100644
--- a/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php
+++ b/core/modules/entity/lib/Drupal/entity/DatabaseStorageController.php
@@ -45,6 +45,15 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
   protected $entityInfo;
 
   /**
+   * An array of property information, i.e. containing definitions.
+   *
+   * @var array
+   *
+   * @see hook_entity_property_info()
+   */
+  protected $propertyInfo;
+
+  /**
    * Additional arguments to pass to hook_TYPE_load().
    *
    * Set before calling Drupal\entity\DatabaseStorageController::attachLoad().
@@ -210,7 +219,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
       // Remove any invalid ids from the array.
       $passed_ids = array_intersect_key($passed_ids, $entities);
       foreach ($entities as $entity) {
-        $passed_ids[$entity->{$this->idKey}] = $entity;
+        $passed_ids[$entity->id()] = $entity;
       }
       $entities = $passed_ids;
     }
@@ -451,7 +460,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
 
       if (!$entity->isNew()) {
         $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey);
-        $this->resetCache(array($entity->{$this->idKey}));
+        $this->resetCache(array($entity->id()));
         $this->postSave($entity, TRUE);
         $this->invokeHook('update', $entity);
       }
@@ -528,4 +537,50 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
     // Invoke the respective entity-level hook.
     module_invoke_all('entity_' . $hook, $entity, $this->entityType);
   }
+
+  /**
+   * Implements Drupal\entity\EntityStorageControllerInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions(array $definition) {
+    // @todo: Add caching for $this->propertyInfo.
+    if (!isset($this->propertyInfo)) {
+      $this->propertyInfo = array(
+        'definitions' => $this->basePropertyDefinitions(),
+        // Contains definitions of optional (per-bundle) properties.
+        'optional' => array(),
+        // An array keyed by bundle name containing the names of the per-bundle
+        // properties.
+        'bundle map' => array(),
+      );
+
+      // Invoke hooks.
+      $result = module_invoke_all($this->entityType . '_property_info');
+      $this->propertyInfo = array_merge_recursive($this->propertyInfo, $result);
+      $result = module_invoke_all('entity_property_info', $this->entityType);
+      $this->propertyInfo = array_merge_recursive($this->propertyInfo, $result);
+
+      $hooks = array('entity_property_info', $this->entityType . '_property_info');
+      drupal_alter($hooks, $this->propertyInfo, $this->entityType);
+    }
+
+    $definitions = $this->propertyInfo['definitions'];
+
+    // Add in per-bundle properties.
+    // @todo: Should this be statically cached as well?
+    if (!empty($definition['bundle']) && isset($this->propertyInfo['bundle map'][$definition['bundle']])) {
+      $definitions += array_intersect_key($this->propertyInfo['optional'], array_flip($this->propertyInfo['bundle map'][$definition['bundle']]));
+    }
+
+    return $definitions;
+  }
+
+  /**
+   * Defines the base properties of the entity type.
+   *
+   * @todo: Define abstract once all entity types have been converted.
+   */
+  public function basePropertyDefinitions() {
+    return array();
+  }
 }
+
diff --git a/core/modules/entity/lib/Drupal/entity/Entity.php b/core/modules/entity/lib/Drupal/entity/Entity.php
index 14009b6..34e7b8c 100644
--- a/core/modules/entity/lib/Drupal/entity/Entity.php
+++ b/core/modules/entity/lib/Drupal/entity/Entity.php
@@ -50,7 +50,7 @@ class Entity implements EntityInterface {
   /**
    * Constructs a new entity object.
    */
-  public function __construct(array $values = array(), $entity_type) {
+  public function __construct(array $values, $entity_type) {
     $this->entityType = $entity_type;
     // Set initial values.
     foreach ($values as $key => $value) {
diff --git a/core/modules/entity/lib/Drupal/entity/EntityInterface.php b/core/modules/entity/lib/Drupal/entity/EntityInterface.php
index 699c424..8c7162e 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityInterface.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityInterface.php
@@ -133,6 +133,9 @@ interface EntityInterface {
    * @return
    *   The property value, or NULL if it is not defined.
    *
+   * @throws \InvalidArgumentException
+   *   If the value of a not existing property should be returned.
+   *
    * @see Drupal\entity\EntityInterface::language()
    */
   public function get($property_name, $langcode = NULL);
@@ -204,5 +207,4 @@ interface EntityInterface {
    *   TRUE if the entity is the current revision, FALSE otherwise.
    */
   public function isCurrentRevision();
-
 }
diff --git a/core/modules/entity/lib/Drupal/entity/EntityNG.php b/core/modules/entity/lib/Drupal/entity/EntityNG.php
new file mode 100644
index 0000000..72eb63c
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/EntityNG.php
@@ -0,0 +1,348 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\EntityNG.
+ */
+
+namespace Drupal\entity;
+
+use Drupal\Core\Data\DataItemInterface;
+use Drupal\Core\Data\DataStructureInterface;
+use Drupal\Component\Uuid\Uuid;
+
+/**
+ * Implements Property API specific enhancements to the Entity class.
+ *
+ * @todo: Once all entity types have been converted, merge improvements into the
+ * Entity class and let EntityInterface extend the DataStructureInterface.
+ */
+class EntityNG extends Entity implements DataStructureInterface {
+
+  /**
+   * The plain data values of the contained properties.
+   *
+   * This always holds the original, unchanged values of the entity. The values
+   * are keyed by language code, whereas LANGUAGE_NOT_SPECIFIED is used for
+   * values in default language.
+   *
+   * @todo: Add methods for getting original properties and for determining
+   * changes.
+   *
+   * @var array
+   */
+  protected $values = array();
+
+  /**
+   * The array of properties, each being an instance of
+   * EntityPropertyListInterface.
+   *
+   * @var array
+   */
+  protected $properties = array();
+
+  /**
+   * Whether the entity is in pre-Property API compatibility mode.
+   *
+   * If set to TRUE, property values are written directly to $this->values, thus
+   * must be plain property values keyed by language code. This must be enabled
+   * when calling field API attachers.
+   *
+   * @var bool
+   */
+  protected $compatibilityMode = FALSE;
+
+
+  /**
+   * Overrides Entity::id().
+   */
+  public function id() {
+    return $this->get('id')->value;
+  }
+
+  /**
+   * Implements EntityInterface::get().
+   */
+  public function get($property_name, $langcode = NULL) {
+    // Values in default language are stored using LANGUAGE_NOT_SPECIFIED,
+    // so use LANGUAGE_NOT_SPECIFIED if either no language is given or it
+    // matches the default language. Then, if the default language is
+    // LANGUAGE_NOT_SPECIFIED, the entity is not translatable, so we always use
+    // LANGUAGE_NOT_SPECIFIED.
+    if (!isset($langcode) || $langcode == $this->language->langcode || LANGUAGE_NOT_SPECIFIED == $this->language->langcode) {
+      // @todo: Find a more meaningful constant name and make field loading use
+      // it too.
+      $langcode = LANGUAGE_NOT_SPECIFIED;
+    }
+    else {
+      $languages = language_list(LANGUAGE_ALL);
+      if (!isset($languages[$langcode])) {
+        throw new \InvalidArgumentException("Unable to get translation for the invalid language '$langcode'.");
+      }
+    }
+
+    // Populate $this->properties to fasten further lookups and to keep track of
+    // property objects, possibly holding changes to properties.
+    if (!isset($this->properties[$property_name][$langcode])) {
+      $definition = $this->getPropertyDefinition($property_name);
+      if (!$definition) {
+        throw new \InvalidArgumentException('Property ' . check_plain($property_name) . ' is unknown.');
+      }
+      // Non-translatable properties always use LANGUAGE_NOT_SPECIFIED.
+      $langcode = empty($definition['translatable']) ? LANGUAGE_NOT_SPECIFIED : $langcode;
+
+      $value = isset($this->values[$property_name][$langcode]) ? $this->values[$property_name][$langcode] : NULL;
+      $this->properties[$property_name][$langcode] = drupal_get_property($definition, $value);
+    }
+    return $this->properties[$property_name][$langcode];
+  }
+
+  /**
+   * Implements EntityInterface::set().
+   */
+  public function set($property_name, $value, $langcode = NULL) {
+    $value = $value instanceof DataItemInterface ? $value->getValue() : $value;
+    $this->get($property_name, $langcode)->setValue($value);
+  }
+
+  /**
+   * Implements DataStructureInterface::getProperties().
+   */
+  public function getProperties() {
+    $properties = array();
+    foreach ($this->getPropertyDefinitions() as $name => $definition) {
+      if (empty($definition['computed'])) {
+        $properties[$name] = $this->get($name);
+      }
+    }
+    return $properties;
+  }
+
+  /**
+   * Implements DataStructureInterface::setProperties().
+   */
+  public function setProperties($properties) {
+    foreach ($properties as $name => $property) {
+      // Copy the value to our property object.
+      $value = $property instanceof DataItemInterface ? $property->getValue() : $property;
+      $this->get($name)->setValue($value);
+    }
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->getProperties());
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definitions = $this->getPropertyDefinitions();
+    return isset($definitions[$name]) ? $definitions[$name] : FALSE;
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    return entity_get_controller($this->entityType)->getPropertyDefinitions(array(
+      'entity type' => $this->entityType,
+      'bundle' => $this->bundle(),
+    ));
+  }
+
+  /**
+   * Implements DataStructureInterface::toArray().
+   */
+  public function toArray() {
+    $values = array();
+    foreach ($this->getProperties() as $name => $property) {
+      $values[$name] = $property->getValue();
+    }
+    return $values;
+  }
+
+  /**
+   * Implements EntityInterface::language().
+   */
+  public function language() {
+    // @todo: Check for language.module instead, once Field API language
+    // handling depends upon it too.
+    return module_exists('locale') ? $this->language->object : FALSE;
+  }
+
+  /**
+   * Gets a translation of the entity.
+   *
+   * @return \Drupal\Core\Data\DataStructureInterface
+   *   A container holding the translated properties.
+   */
+  public function getTranslation($langcode) {
+
+    if ($langcode == LANGUAGE_NOT_SPECIFIED || $langcode == $this->get('language')->langcode) {
+      // No translation need, return the entity.
+      return $this;
+    }
+    // Check whether the language code is valid, thus is of an available
+    // language.
+    $languages = language_list(LANGUAGE_ALL);
+    if (!isset($languages[$langcode])) {
+      throw new \InvalidArgumentException("Unable to get translation for the invalid language '$langcode'.");
+    }
+
+    $definition = array(
+      'entity type' => $this->entityType,
+      'bundle' => $this->bundle(),
+      'langcode' => $langcode,
+    );
+    return new EntityTranslation($definition, NULL, array('parent' => $this));
+  }
+
+  /**
+   * Overrides EntityInterface::translations().
+   */
+  public function translations() {
+    $translations = array();
+    // Build an array with the translation langcodes set as keys.
+    foreach ($this->getProperties() as $name => $property) {
+      if (isset($this->values[$name])) {
+        $translations += $this->values[$name];
+      }
+      $translations += $this->properties[$name];
+    }
+    unset($translations[LANGUAGE_NOT_SPECIFIED]);
+
+    // Now get languages based upon translation langcodes.
+    $languages = array_intersect_key(language_list(), $translations);
+    return $languages;
+  }
+
+  public function access($account) {
+    // TODO: Implement access() method.
+  }
+
+  /**
+   * Enables or disable the compatibility mode.
+   *
+   * @param bool $enabled
+   *   Whether to enable the mode.
+   *
+   * @see EntityNG::compatibilityMode
+   */
+  public function setCompatibilityMode($enabled) {
+    $this->compatibilityMode = (bool) $enabled;
+  }
+
+  /**
+   * Returns whether the compatibility mode is active.
+   */
+  public function getCompatibilityMode() {
+    return $this->compatibilityMode;
+  }
+
+  /**
+   * Updates the original values with the interim changes.
+   *
+   * Note: This should be called by the storage controller during a save
+   * operation.
+   */
+  public function updateOriginalValues() {
+    foreach ($this->properties as $name => $properties) {
+      foreach ($properties as $langcode => $property) {
+        $this->values[$name][$langcode] = $property->getValue();
+      }
+    }
+  }
+
+  /**
+   * Magic getter: Gets the property in default language.
+   *
+   * For compatibility mode to work this must return a reference.
+   */
+  public function &__get($name) {
+    if ($this->compatibilityMode) {
+      if (!isset($this->values[$name])) {
+        $this->values[$name] = NULL;
+      }
+      return $this->values[$name];
+    }
+    if ($this->getPropertyDefinition($name)) {
+      $return = $this->get($name);
+      return $return;
+    }
+    if (!isset($this->$name)) {
+      $this->$name = NULL;
+    }
+    return $this->$name;
+  }
+
+  /**
+   * Magic getter: Sets the property in default language.
+   */
+  public function __set($name, $value) {
+    if ($this->compatibilityMode) {
+      $this->values[$name] = $value;
+    }
+    elseif ($this->getPropertyDefinition($name)) {
+      $this->set($name, $value);
+    }
+    else {
+      $this->$name = $value;
+    }
+  }
+
+  /**
+   * Magic method.
+   */
+  public function __isset($name) {
+    if ($this->compatibilityMode) {
+      return isset($this->values[$name]);
+    }
+    else {
+      return isset($this->properties[$name]);
+    }
+  }
+
+  /**
+   * Magic method.
+   */
+  public function __unset($name) {
+    if ($this->compatibilityMode) {
+      unset($this->values[$name]);
+    }
+    else {
+      unset($this->properties[$name]);
+    }
+  }
+
+  /**
+   * Overrides Entity::createDuplicate().
+   */
+  public function createDuplicate() {
+    $duplicate = clone $this;
+    $entity_info = $this->entityInfo();
+    $this->{$entity_info['entity keys']['id']}->value = NULL;
+
+    // Check if the entity type supports UUIDs and generate a new one if so.
+    if (!empty($entity_info['entity keys']['uuid'])) {
+      $uuid = new Uuid();
+      $duplicate->{$entity_info['entity keys']['uuid']}->value = $uuid->generate();
+    }
+    return $duplicate;
+  }
+
+  /**
+   * Implements a deep clone.
+   */
+  public function __clone() {
+    foreach ($this->properties as $name => $properties) {
+      foreach ($properties as $langcode => $property) {
+        $this->properties[$name][$langcode] = clone $property;
+      }
+    }
+  }
+}
+
diff --git a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php b/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php
index 7f27e96..0ec05b9 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php
@@ -88,4 +88,27 @@ interface EntityStorageControllerInterface {
    */
   public function save(EntityInterface $entity);
 
+  /**
+   * Gets an array entity property definitions.
+   *
+   * If a 'bundle' key is present in the given entity definition, properties
+   * specific to this bundle are included.
+   *
+   * @param array $definition
+   *   The definition of the entity of which to get property definitions, i.e.
+   *   an array having an 'entity type' and optionally a 'bundle' key. For
+   *   example:
+   *   @code
+   *   $definition = array(
+   *     'type' => 'entity',
+   *     'entity type' => 'node',
+   *     'bundle' => 'article',
+   *   );
+   *   @endcode
+   *
+   * @return array
+   *   An array of property definitions of entity properties, keyed by property
+   *   name.
+   */
+  public function getPropertyDefinitions(array $definition);
 }
diff --git a/core/modules/entity/lib/Drupal/entity/EntityTranslation.php b/core/modules/entity/lib/Drupal/entity/EntityTranslation.php
new file mode 100644
index 0000000..9caf977
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/EntityTranslation.php
@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\EntityTranslation.
+ */
+
+namespace Drupal\entity;
+
+use Drupal\Core\Data\DataItemInterface;
+use Drupal\Core\Data\DataStructureInterface;
+
+/**
+ * Makes translated entity properties available via the Property API.
+ */
+class EntityTranslation implements DataStructureInterface, DataItemInterface {
+
+  /**
+   * The property definition.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * The entity of which we make property translations available.
+   *
+   * @var EntityNG
+   */
+  protected $entity;
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+
+    if (empty($context['parent'])) {
+      throw new \InvalidArgumentException('Missing context, i.e. the entity to work with.');
+    }
+    $this->entity = $context['parent'];
+
+    if (empty($this->definition['langcode'])) {
+      throw new \InvalidArgumentException('Missing language code');
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    $values = array();
+    foreach ($this->getProperties() as $name => $property) {
+      $values[$name] = $property->getValue();
+    }
+    return $values;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   */
+  public function setValue($values) {
+    foreach ($this->getProperties() as $name => $property) {
+      $property->setValue(isset($values[$name]) ? $values[$name] : NULL);
+      unset($values[$name]);
+    }
+    if ($values) {
+      throw new \InvalidArgumentException('Property ' . check_plain(key($values)) . ' is unknown or not translatable.');
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getString().
+   */
+  public function getString() {
+    $strings = array();
+    foreach ($this->getProperties() as $property) {
+      $strings[] = $property->getString();
+    }
+    return implode(', ', array_filter($strings));
+  }
+
+  /**
+   * Implements DataItemInterface::get().
+   */
+  public function get($property_name) {
+    $definitions = $this->getPropertyDefinitions();
+    if (!isset($definitions[$property_name])) {
+      throw new \InvalidArgumentException('Property ' . check_plain(key($values)) . ' is unknown or not translatable.');
+    }
+    return $this->entity->get($property_name, $this->definition['langcode']);
+  }
+
+  /**
+   * Implements DataStructureInterface::getProperties().
+   */
+  public function getProperties() {
+    $properties = array();
+    foreach ($this->getPropertyDefinitions() as $name => $definition) {
+      if (empty($definition['computed'])) {
+        $properties[$name] = $this->get($name);
+      }
+    }
+    return $properties;
+  }
+
+  /**
+   * Implements DataStructureInterface::setProperties().
+   */
+  public function setProperties($properties) {
+    foreach ($properties as $name => $property) {
+      // Copy the value to our property object.
+      $value = $property instanceof DataItemInterface ? $property->getValue() : $property;
+      $this->get($name)->setValue($value);
+    }
+  }
+
+  /**
+   * Magic getter: Gets the property in default language.
+   */
+  public function __get($name) {
+    return $this->get($name);
+  }
+
+  /**
+   * Magic getter: Sets the property in default language.
+   */
+  public function __set($name, $value) {
+    $value = $value instanceof DataItemInterface ? $value->getValue() : $value;
+    $this->get($name)->setValue($value);
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->getProperties());
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definitions = $this->getPropertyDefinitions();
+    return isset($definitions[$name]) ? $definitions[$name] : FALSE;
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    $definitions = array();
+    foreach ($this->entity->getPropertyDefinitions() as $name => $definition) {
+      if (!empty($definition['translatable'])) {
+        $definitions[$name] = $definition;
+      }
+    }
+    return $definitions;
+  }
+
+  /**
+   * Implements DataStructureInterface::toArray().
+   */
+  public function toArray() {
+    return $this->getValue();
+  }
+
+  public function access($account = NULL) {
+    // @todo implement
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // @todo implement
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyItemBase.php b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyItemBase.php
new file mode 100644
index 0000000..ab2e969
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyItemBase.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\Property\EntityPropertyItemBase.
+ */
+
+namespace Drupal\entity\Property;
+use \Drupal\Core\Data\DataItemInterface;
+use \Drupal\Core\Data\DataStructureInterface;
+
+/**
+ * An entity property item.
+ *
+ * Entity property items making use of this base class have to implement the
+ * DataStructureInterface::getPropertyDefinitions().
+ *
+ * @see EntityPropertyItemInterface
+ */
+abstract class EntityPropertyItemBase implements EntityPropertyItemInterface {
+
+  /**
+   * The array of properties.
+   *
+   * Property objects are instantiated during object construction and cannot be
+   * replaced by others, so computed properties can safely store references on
+   * other properties.
+   *
+   * @var array<DataItemInterface>
+   */
+  protected $properties = array();
+
+  /**
+   * The definition of the entity property.
+   *
+   * @var array
+   */
+  protected $definition;
+
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+
+    // Initialize all property objects, but postpone the creating of computed
+    // properties to a second step. That way computed properties can safely get
+    // references on non-computed properties during construction.
+    $step2 = array();
+    foreach ($this->getPropertyDefinitions() as $name => $definition) {
+      if (empty($definition['computed'])) {
+        $context = array('name' => $name, 'parent' => $this);
+        $this->properties[$name] = drupal_get_property($definition, NULL, $context);
+      }
+      else {
+        $step2[$name] = $definition;
+      }
+    }
+
+    foreach ($step2 as $name => $definition) {
+      $context = array('name' => $name, 'parent' => $this);
+      $this->properties[$name] = drupal_get_property($definition, NULL, $context);
+    }
+
+    if (isset($value)) {
+      $this->setValue($value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    $values = array();
+    foreach ($this->getProperties() as $name => $property) {
+      $values[$name] = $property->getValue();
+    }
+    return $values;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   *
+   * @param array $values
+   *   An array of property values.
+   */
+  public function setValue($values) {
+    // Treat the values as property value of the first property, if no array is
+    // given and we only have one property.
+    if (!is_array($values) && count($this->properties) == 1) {
+      $keys = array_keys($this->properties);
+      $values = array($keys[0] => $values);
+    }
+    // Support passing in property objects as value.
+    elseif ($values instanceof DataItemInterface) {
+      $values = $values->getValue();
+    }
+
+    foreach ($this->properties as $name => $property) {
+      $property->setValue(isset($values[$name]) ? $values[$name] : NULL);
+    }
+    // @todo: Throw an exception for invalid values once conversion is
+    // totally completed.
+  }
+
+  /**
+   * Implements DataItemInterface::getString().
+   */
+  public function getString() {
+    $strings = array();
+    foreach ($this->getProperties() as $property) {
+      $strings[] = $property->getString();
+    }
+    return implode(', ', array_filter($strings));
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // @todo implement
+  }
+
+  /**
+   * Gets a property.
+   */
+  public function get($property_name) {
+    if (!isset($this->properties[$property_name])) {
+      throw new \InvalidArgumentException('Property ' . check_plain($property_name) . ' is unknown.');
+    }
+    return $this->properties[$property_name];
+  }
+
+  /**
+   * Implements EntityPropertyItemInterface::__get().
+   */
+  public function __get($name) {
+    return $this->get($name)->getValue();
+  }
+
+  /**
+   * Implements EntityPropertyItemInterface::__set().
+   */
+  public function __set($name, $value) {
+    $this->get($name)->setValue($value);
+  }
+
+  /**
+   * Implements DataStructureInterface::getProperties().
+   */
+  public function getProperties() {
+    $properties = array();
+    foreach ($this->getPropertyDefinitions() as $name => $definition) {
+      if (empty($definition['computed'])) {
+        $properties[$name] = $this->properties[$name];
+      }
+    }
+    return $properties;
+  }
+
+  /**
+   * Implements DataStructureInterface::setProperties().
+   */
+  public function setProperties($properties) {
+    foreach ($properties as $name => $property) {
+      if (isset($this->properties[$name])) {
+        // Copy the value to our property object.
+        $value = $property instanceof DataItemInterface ? $property->getValue() : $property;
+        $this->properties[$name]->setValue($value);
+      }
+      else {
+        throw new \InvalidArgumentException('Property ' . check_plain($name) . ' is unknown.');
+      }
+    }
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->getProperties());
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definitions = $this->getPropertyDefinitions();
+    return isset($definitions[$name]) ? $definitions[$name] : FALSE;
+  }
+
+  /**
+   * Implements DataStructureInterface::toArray().
+   */
+  public function toArray() {
+    return $this->getValue();
+  }
+
+  /**
+   * Implements a deep clone.
+   */
+  public function __clone() {
+    foreach ($this->properties as $name => $property) {
+      $this->properties[$name] = clone $property;
+    }
+  }
+
+  public function access($account = NULL) {
+    // @todo implement
+  }
+}
\ No newline at end of file
diff --git a/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyItemInterface.php b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyItemInterface.php
new file mode 100644
index 0000000..47f373b
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyItemInterface.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\Property\EntityPropertyItemInterface.
+ */
+
+namespace Drupal\entity\Property;
+
+use Drupal\Core\Data\DataStructureInterface;
+use Drupal\Core\Data\DataItemInterface;
+
+/**
+ * Interface for entity property items, which are property container that may
+ * contain only primitives and entity references.
+ *
+ * @see EntityPropertyList
+ * @see EntityPropertyItem
+ */
+interface EntityPropertyItemInterface extends DataStructureInterface, DataItemInterface {
+
+  /**
+   * Check entity property access.
+   *
+   * @param \Drupal\user\User $account
+   *   (optional) The user account to check access for. Defaults to the current
+   *   user.
+   *
+   * @return bool
+   *   Whether the given user has access.
+   */
+  public function access($account = NULL);
+
+  /**
+   * Returns a property.
+   *
+   * Entity property items may contain only primitives and entity references.
+   *
+   * @param string $property_name
+   *   The name of the property to return; e.g., 'value'.
+   *
+   * @return Drupal\Core\Data\DataItemInterface
+   *   The property object.
+   */
+  public function get($property_name);
+
+  /**
+   * Magic getter: Get the property value.
+   */
+  public function __get($name);
+
+  /**
+   * Magic setter: Set the property value.
+   */
+  public function __set($name, $value);
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyList.php b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyList.php
new file mode 100644
index 0000000..622f824
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyList.php
@@ -0,0 +1,272 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\Property\EntityPropertyList.
+ */
+
+namespace Drupal\entity\Property;
+
+/**
+ * An entity property list.
+ *
+ * An entity property is a list of property items, which contain only primitive
+ * properties or entity references. Note that even single-valued entity
+ * properties are represented as list of items, however for easy access to the
+ * contained item the entity property delegates __get() and __set() calls
+ * directly to the first item.
+ *
+ * @see EntityPropertyListInterface.
+ */
+class EntityPropertyList implements EntityPropertyListInterface {
+
+  /**
+   * Numerically indexed array of property items, implementing the
+   * EntityPropertyItemInterface.
+   *
+   * @var array
+   */
+  protected $list = array();
+
+  /**
+   * The definition of the entity property.
+   *
+   * @var array
+   */
+  protected $definition;
+
+   /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+    if (isset($value)) {
+      $this->setValue($value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    $values = array();
+    foreach ($this->list as $delta => $item) {
+      // @todo: Filter out empty items and add an isEmpty() method to them.
+      $values[$delta] = $item->getValue();
+    }
+    return $values;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   *
+   * @param array $values
+   *   An array of values of the property items.
+   */
+  public function setValue($values) {
+    if (isset($values)) {
+
+      // Support passing in property objects as value.
+      if ($values instanceof DataItemInterface) {
+        $values = $values->getValue();
+      }
+      if (!is_array($values)) {
+        throw new \InvalidArgumentException("An entity property requires a numerically indexed array of items as value.");
+      }
+
+      // Clear the values of properties for which no value has been passed.
+      foreach (array_diff_key($this->list, $values) as $delta => $item) {
+        unset($this->list[$delta]);
+      }
+
+      // Set the values.
+      foreach ($values as $delta => $value) {
+        if (!is_numeric($delta)) {
+          throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
+        }
+        elseif (!isset($this->list[$delta])) {
+          $this->list[$delta] = $this->createItem($value);
+        }
+        else {
+          $this->list[$delta]->setValue($value);
+        }
+      }
+    }
+    else {
+      $this->list = array();
+    }
+  }
+
+  /**
+   * Returns a string representation of the property.
+   *
+   * @return string
+   */
+  public function getString() {
+    $strings = array();
+    foreach ($this->list() as $item) {
+      $strings[] = $item->getString();
+    }
+    return implode(', ', array_filter($strings));
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // @todo implement
+  }
+
+  /**
+   * Implements ArrayAccess::offsetExists().
+   */
+  public function offsetExists($offset) {
+    return array_key_exists($offset, $this->list);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetUnset().
+   */
+  public function offsetUnset($offset) {
+    unset($this->list[$offset]);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetGet().
+   */
+  public function offsetGet($offset) {
+    if (!isset($offset)) {
+      // @todo: Needs tests.
+      // The [] operator has been used so point at a new entry.
+      $offset = $this->list ? max(array_keys($this->list)) + 1 : 0;
+    }
+
+    if (!is_numeric($offset)) {
+      throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
+    }
+    // Allow getting not yet existing items as well.
+    // @todo: Maybe add a public createItem() method instead or in addition?
+    // @todo: Needs tests.
+    elseif (!isset($this->list[$offset])) {
+      $this->list[$offset] = $this->createItem();
+    }
+
+    return $this->list[$offset];
+  }
+
+  /**
+   * Helper for creating a list item object.
+   *
+   * @return \Drupal\Core\Data\DataItemInterface
+   */
+  protected function createItem($value = NULL) {
+    return drupal_get_property(array('list' => FALSE) + $this->definition, $value);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetSet().
+   */
+  public function offsetSet($offset, $value) {
+    // @todo: Throw exception if the value does not implement the interface.
+    if (is_numeric($offset)) {
+      $this->offsetGet($offset)->setValue($value);
+    }
+    else {
+      throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
+    }
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->list);
+  }
+
+  /**
+   * Implements Countable::count().
+   */
+  public function count() {
+    return count($this->list);
+  }
+
+  /**
+   * Delegate.
+   */
+  public function getProperties() {
+    return $this->offsetGet(0)->getProperties();
+  }
+
+  /**
+   * Delegate.
+   */
+  public function getPropertyDefinition($name) {
+    return $this->offsetGet(0)->getPropertyDefinition($name);
+  }
+
+  /**
+   * Delegate.
+   */
+  public function getPropertyDefinitions() {
+    return $this->offsetGet(0)->getPropertyDefinitions();
+  }
+
+  /**
+   * Delegate.
+   */
+  public function __get($property_name) {
+    return $this->offsetGet(0)->__get($property_name);
+  }
+
+  /**
+   * Delegate.
+   */
+  public function get($property_name) {
+    return $this->offsetGet(0)->get($property_name);
+  }
+
+  /**
+   * Delegate.
+   */
+  public function __set($property_name, $value) {
+    $this->offsetGet(0)->__set($property_name, $value);
+  }
+
+  /**
+   * Gets the the raw array representation of the entity property.
+   *
+   * @return array
+   *   The raw array representation of the entity property, i.e. an array
+   *   containing the raw values of all contained items.
+   */
+  public function toArray() {
+    return $this->getValue();
+  }
+
+  /**
+   * Implements a deep clone.
+   */
+  public function __clone() {
+    foreach ($this->list as $delta => $property) {
+      $this->list[$delta] = clone $property;
+    }
+  }
+
+  public function access($account = NULL) {
+    // TODO: Implement access() method. Use item access.
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyListInterface.php b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyListInterface.php
new file mode 100644
index 0000000..4e1c01f
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/EntityPropertyListInterface.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\Property\EntityPropertyListInterface.
+ */
+
+namespace Drupal\entity\Property;
+use Drupal\Core\Data\DataListInterface;
+
+/**
+ * Interface for entity properties, being lists of property items.
+ *
+ * Contained items must implement the EntityPropertyItemInterface. This
+ * interface is required for every property of an entity.
+ *
+ * Some methods are delegated to the first contained EntityPropertyItem, in
+ * particular get() and set() as well as their magic equivalences.
+ *
+ * @todo: Should getProperties(), setProperties() and getPropertyDefinitions()
+ * be delegated as well.
+ */
+interface EntityPropertyListInterface extends DataListInterface {
+
+  /**
+   * Delegated to the first item.
+   *
+   * @see EntityPropertyItemInterface::get()
+   */
+  public function get($property_name);
+
+  /**
+   * Magic getter: Delegated to the first item.
+   */
+  public function __get($name);
+
+  /**
+   * Magic setter: Delegated to the first item.
+   */
+  public function __set($name, $value);
+
+  /**
+   * Check entity property access.
+   *
+   * @param \Drupal\user\User $account
+   *   (optional) The user account to check access for. Defaults to the current
+   *   user.
+   *
+   * @return bool
+   *   Whether the given user has access.
+   */
+  public function access($account = NULL);
+
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/PropertyEntity.php b/core/modules/entity/lib/Drupal/entity/Property/PropertyEntity.php
new file mode 100644
index 0000000..2628215
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/PropertyEntity.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\entity\Property\PropertyEntity.
+ */
+
+namespace Drupal\entity\Property;
+use \Drupal\Core\Data\DataItemInterface;
+use \Drupal\Core\Data\DataStructureInterface;
+
+
+/**
+ * Defines the 'entity' property type, e.g. the computed 'entity' property of entity references.
+ *
+ * This property implements the container interface, whereby most container
+ * methods are just forwarded to the contained entity (if set).
+ */
+class PropertyEntity implements DataItemInterface, DataStructureInterface {
+
+  /**
+   * The property definition.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * The referenced entity type.
+   *
+   * @var string
+   */
+  protected $entityType;
+
+  /**
+   * The property holding the entity ID.
+   *
+   * @var \Drupal\Core\Data\DataItemInterface
+   */
+  protected $id;
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+    $this->entityType = isset($this->definition['entity type']) ? $this->definition['entity type'] : NULL;
+
+    if (isset($context['parent'])) {
+      $this->id = $context['parent']->get('id');
+    }
+    else {
+      // No context given, so just initialize an ID property for storing the
+      // entity ID.
+      $this->id = drupal_get_property(array('type' => 'string'));
+    }
+
+    if (isset($value)) {
+      $this->setValue($value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements DataItemInterface::getDefinition().
+   *
+   * Property definitions of type 'entity' may contain keys further defining the
+   * reference. Additionally supported keys are:
+   *   - entity type: The entity type which is being referenced.
+   *   - bundle: The bundle which is being referenced, or an array of possible
+   *     bundles.
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue() {
+    $id = $this->id->getValue();
+    return $id ? entity_load($this->entityType, $id) : NULL;
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   *
+   * Both the entity ID and the entity object may be passed as value.
+   */
+  public function setValue($value) {
+    if (!isset($value)) {
+      $this->id->setValue(NULL);
+    }
+    elseif (is_scalar($value) && !empty($this->definition['entity type'])) {
+      $this->id->setValue($value);
+    }
+    elseif ($value instanceof \Drupal\entity\EntityInterface) {
+      $this->id->setValue($value->id());
+      $this->entityType = $value->entityType();
+    }
+    else {
+      throw new \InvalidArgumentException('Value is no valid entity.');
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::getString().
+   */
+  public function getString() {
+    $entity = $this->getValue();
+    return $entity ? $entity->label() : '';
+  }
+
+  /**
+   * Implements DataItemInterface::validate().
+   */
+  public function validate($value = NULL) {
+    // TODO: Implement validate() method.
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    $entity = $this->getValue();
+    return $entity ? $entity->getIterator() : new \ArrayIterator(array());
+  }
+
+  /**
+   * Implements DataStructureInterface::getProperties().
+   */
+  public function getProperties() {
+    $entity = $this->getValue();
+    return $entity ? $entity->getProperties() : array();
+  }
+
+  /**
+   * Implements DataStructureInterface::setProperties().
+   */
+  public function setProperties($properties) {
+    if ($entity = $this->getValue()) {
+      $entity->setProperties($properties);
+    }
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definitions = $this->getPropertyDefinitions();
+    return isset($definitions[$name]) ? $definitions[$name] : FALSE;
+  }
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // @todo: Support getting definitions if multiple bundles are specified.
+    $definitions = entity_get_controller($this->definition['entity type'])->getPropertyDefinitions($this->definition);
+
+    return $definitions;
+  }
+
+  /**
+   * Implements DataStructureInterface::toArray().
+   */
+  public function toArray() {
+    $entity = $this->getValue();
+    return $entity ? $entity->toArray() : array();
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/PropertyEntityReferenceItem.php b/core/modules/entity/lib/Drupal/entity/Property/PropertyEntityReferenceItem.php
new file mode 100644
index 0000000..926be78
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/PropertyEntityReferenceItem.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\entity\Property\PropertyEntityReferenceItem.
+ */
+
+namespace Drupal\entity\Property;
+use \Drupal\entity\Property\EntityPropertyItemBase;
+
+
+/**
+ * Defines the 'entityreference_item' entity property item.
+ */
+class PropertyEntityReferenceItem extends EntityPropertyItemBase {
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // @todo: Avoid creating multiple array copies if used multiple times.
+
+    $definitions['id'] = array(
+      // @todo: Lookup the entity type's ID data type and use it here.
+      'type' => 'integer',
+      'label' => t('Entity ID'),
+    );
+    $definitions['entity'] = array(
+      'type' => 'entity',
+      'entity type' => $this->definition['entity type'],
+      'label' => t('Entity'),
+      'description' => t('The referenced entity'),
+      // The entity object is computed out of the entity id.
+      'computed' => TRUE,
+      'read-only' => FALSE,
+    );
+    return $definitions;
+  }
+
+  /**
+   * Overrides EntityPropertyItemBase::setValue().
+   */
+  public function setValue($values) {
+    // Treat the values as property value of the entity property, if no array
+    // is given.
+    if (!is_array($values)) {
+      $values = array('entity' => $values);
+    }
+
+    // Entity is computed out of the ID, so we only need to update the ID. Only
+    // set the entity property if no ID is given.
+    if (!empty($values['id'])) {
+      $this->properties['id']->setValue($values['id']);
+    }
+    else {
+      $this->properties['entity']->setValue(isset($values['entity']) ? $values['entity'] : NULL);
+    }
+    unset($values['entity'], $values['id']);
+    if ($values) {
+      throw new \InvalidArgumentException('Property ' . key($values) . ' is unknown.');
+    }
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/PropertyIntegerItem.php b/core/modules/entity/lib/Drupal/entity/Property/PropertyIntegerItem.php
new file mode 100644
index 0000000..45fb047
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/PropertyIntegerItem.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\entity\Property\PropertyIntegerItem.
+ */
+
+namespace Drupal\entity\Property;
+use \Drupal\entity\Property\EntityPropertyItemBase;
+
+
+/**
+ * Defines the 'integer_item' entity property item.
+ */
+class PropertyIntegerItem extends EntityPropertyItemBase {
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // Statically cache the definitions to avoid creating lots of array copies.
+    $definitions = drupal_static(__CLASS__);
+
+    if (!isset($definitions)) {
+      $definitions['value'] = array(
+        'type' => 'integer',
+        'label' => t('Integer value'),
+      );
+    }
+    return $definitions;
+  }
+}
+
diff --git a/core/modules/entity/lib/Drupal/entity/Property/PropertyLanguageItem.php b/core/modules/entity/lib/Drupal/entity/Property/PropertyLanguageItem.php
new file mode 100644
index 0000000..9799966
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/PropertyLanguageItem.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\entity\Property\PropertyLanguageItem.
+ */
+
+namespace Drupal\entity\Property;
+use \Drupal\entity\Property\EntityPropertyItemBase;
+
+
+/**
+ * Defines the 'language_item' entity property item.
+ */
+class PropertyLanguageItem extends EntityPropertyItemBase {
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // Statically cache the definitions to avoid creating lots of array copies.
+    $definitions = drupal_static(__CLASS__);
+
+    if (!isset($definitions)) {
+      $definitions['langcode'] = array(
+        'type' => 'string',
+        'label' => t('Language code'),
+      );
+      $definitions['object'] = array(
+        'type' => 'language',
+        'label' => t('Language object'),
+        // The language object is retrieved via the language code.
+        'computed' => TRUE,
+        'read-only' => FALSE,
+      );
+    }
+    return $definitions;
+  }
+
+  /**
+   * Overrides EntityPropertyItemBase::setValue().
+   */
+  public function setValue($values) {
+    // Treat the values as property value of the object property, if no array
+    // is given.
+    if (!is_array($values)) {
+      $values = array('object' => $values);
+    }
+
+    // Language is computed out of the langcode, so we only need to update the
+    // langcode. Only set the language property if no langcode is given.
+    if (!empty($values['langcode'])) {
+      $this->properties['langcode']->setValue($values['langcode']);
+    }
+    else {
+      $this->properties['object']->setValue(isset($values['object']) ? $values['object'] : NULL);
+    }
+    unset($values['object'], $values['langcode']);
+    if ($values) {
+      throw new \InvalidArgumentException('Property ' . key($values) . ' is unknown.');
+    }
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Property/PropertyStringItem.php b/core/modules/entity/lib/Drupal/entity/Property/PropertyStringItem.php
new file mode 100644
index 0000000..dd55b31
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Property/PropertyStringItem.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\entity\Property\PropertyStringItem.
+ */
+
+namespace Drupal\entity\Property;
+use \Drupal\entity\Property\EntityPropertyItemBase;
+
+
+/**
+ * Defines the 'string_item' entity property item.
+ */
+class PropertyStringItem extends EntityPropertyItemBase {
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // Statically cache the definitions to avoid creating lots of array copies.
+    $definitions = drupal_static(__CLASS__);
+
+    if (!isset($definitions)) {
+      $definitions['value'] = array(
+        'type' => 'string',
+        'label' => t('Text value'),
+      );
+    }
+    return $definitions;
+  }
+}
+
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php
index 6a335c5..a33bd61 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityApiTest.php
@@ -36,21 +36,20 @@ class EntityApiTest extends WebTestBase {
     $user1 = $this->drupalCreateUser();
 
     // Create some test entities.
-    $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid));
+    $entity = entity_create('entity_test', array('name' => 'test', 'user' => $user1->uid));
     $entity->save();
-    $entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid));
+    $entity = entity_create('entity_test', array('name' => 'test2', 'user' => $user1->uid));
     $entity->save();
-    $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
+    $entity = entity_create('entity_test', array('name' => 'test', 'user' => NULL));
     $entity->save();
 
     $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test')));
-
-    $this->assertEqual($entities[0]->get('name'), 'test', 'Created and loaded entity.');
-    $this->assertEqual($entities[1]->get('name'), 'test', 'Created and loaded entity.');
+    $this->assertEqual($entities[0]->name->value, 'test', 'Created and loaded entity.');
+    $this->assertEqual($entities[1]->name->value, 'test', 'Created and loaded entity.');
 
     // Test loading a single entity.
-    $loaded_entity = entity_test_load($entity->id);
-    $this->assertEqual($loaded_entity->id, $entity->id, 'Loaded a single entity by id.');
+    $loaded_entity = entity_test_load($entity->id());
+    $this->assertEqual($loaded_entity->id(), $entity->id(), 'Loaded a single entity by id.');
 
     // Test deleting an entity.
     $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2')));
@@ -60,10 +59,10 @@ class EntityApiTest extends WebTestBase {
 
     // Test updating an entity.
     $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test')));
-    $entities[0]->set('name', 'test3');
+    $entities[0]->name->value = 'test3';
     $entities[0]->save();
-    $entity = entity_test_load($entities[0]->id);
-    $this->assertEqual($entity->get('name'), 'test3', 'Entity updated.');
+    $entity = entity_test_load($entities[0]->id());
+    $this->assertEqual($entity->name->value, 'test3', 'Entity updated.');
 
     // Try deleting multiple test entities by deleting all.
     $ids = array_keys(entity_test_load_multiple(FALSE));
@@ -77,21 +76,21 @@ class EntityApiTest extends WebTestBase {
    * Tests Entity getters/setters.
    */
   function testEntityGettersSetters() {
-    $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL));
-    $this->assertNull($entity->get('uid'), 'Property is not set.');
+    $entity = entity_create('entity_test', array('name' => 'test', 'user' => NULL));
+    $this->assertNull($entity->user->id, 'Property is not set.');
 
-    $entity->set('uid', $GLOBALS['user']->uid);
-    $this->assertEqual($entity->get('uid'), $GLOBALS['user']->uid, 'Property has been set.');
+    $entity->user->entity = $GLOBALS['user'];
+    $this->assertEqual($entity->user->id, $GLOBALS['user']->uid, 'Property has been set.');
 
-    $value = $entity->get('uid');
-    $this->assertEqual($value, $entity->get('uid'), 'Property has been retrieved.');
+    $value = $entity->get('user')->entity;
+    $this->assertEqual($value, $GLOBALS['user'], 'Property has been retrieved.');
 
     // Make sure setting/getting translations boils down to setting/getting the
-    // regular value as the entity and property are not translatable.
-    $entity->set('uid', NULL, 'en');
-    $this->assertNull($entity->uid, 'Language neutral property has been set.');
+    // regular value if the property is not translatable.
+    $entity->get('uuid', 'en')->value = NULL;
+    $this->assertNull($entity->uuid->value, 'Language neutral property has been set.');
 
-    $value = $entity->get('uid', 'en');
-    $this->assertNull($value, 'Language neutral property has been retrieved.');
+    $property = $entity->get('uuid', 'en');
+    $this->assertNull($property->value, 'Language neutral property has been retrieved.');
   }
 }
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityPropertyTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityPropertyTest.php
new file mode 100644
index 0000000..41c6b54
--- /dev/null
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityPropertyTest.php
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\Tests\EntityPropertyTest.
+ */
+
+namespace Drupal\entity\Tests;
+
+use Drupal\Core\Data\DataItemInterface;
+use Drupal\entity\EntityInterface;
+use Drupal\entity\Property\EntityPropertyListInterface;
+use Drupal\entity\Property\EntityPropertyItemInterface;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests Entity API base functionality.
+ */
+class EntityPropertyTest extends WebTestBase  {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('entity_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity property API',
+      'description' => 'Tests the Entity property API',
+      'group' => 'Entity API',
+    );
+  }
+
+  /**
+   * Creates a test entity.
+   *
+   * @return \Drupal\entity\EntityInterface
+   */
+  protected function createTestEntity() {
+    $this->entity_name = $this->randomName();
+    $this->entity_user = $this->drupalCreateUser();
+    $this->entity_field_text = $this->randomName();
+
+    // Pass in the value of the name property when creating. With the user
+    // property we test setting a property after creation.
+    $entity = entity_create('entity_test', array());
+    $entity->user->id = $this->entity_user->uid;
+    $entity->name->value = $this->entity_name;
+
+    // Set a value for the test field.
+    $entity->field_test_text->value = $this->entity_field_text;
+
+    return $entity;
+  }
+
+  /**
+   * Tests reading and writing properties and property items.
+   */
+  public function testReadWrite() {
+    $entity = $this->createTestEntity();
+
+    // Access the name property.
+    $this->assertTrue($entity->name instanceof EntityPropertyListInterface, 'Property implements interface');
+    $this->assertTrue($entity->name[0] instanceof EntityPropertyItemInterface, 'Property item implements interface');
+
+    $this->assertEqual($this->entity_name, $entity->name->value, 'Name value can be read.');
+    $this->assertEqual($this->entity_name, $entity->name[0]->value, 'Name value can be read through list access.');
+    $this->assertEqual($entity->name->getValue(), array(0 => array('value' => $this->entity_name)), 'Plain property value returned.');
+
+    // Change the name.
+    $new_name = $this->randomName();
+    $entity->name->value = $new_name;
+    $this->assertEqual($new_name, $entity->name->value, 'Name can be updated and read.');
+    $this->assertEqual($entity->name->getValue(), array(0 => array('value' => $new_name)), 'Plain property value reflects the update.');
+
+    $new_name = $this->randomName();
+    $entity->name[0]->value = $new_name;
+    $this->assertEqual($new_name, $entity->name->value, 'Name can be updated and read through list access.');
+
+    // Access the user property.
+    $this->assertTrue($entity->user instanceof EntityPropertyListInterface, 'Property implements interface');
+    $this->assertTrue($entity->user[0] instanceof EntityPropertyItemInterface, 'Property item implements interface');
+
+    $this->assertEqual($this->entity_user->uid, $entity->user->id, 'User id can be read.');
+    $this->assertEqual($this->entity_user->name, $entity->user->entity->name, 'User name can be read.');
+
+    // Change the assigned user by entity.
+    $new_user = $this->drupalCreateUser();
+    $entity->user->entity = $new_user;
+    $this->assertEqual($new_user->uid, $entity->user->id, 'Updated user id can be read.');
+    $this->assertEqual($new_user->name, $entity->user->entity->name, 'Updated user name value can be read.');
+
+    // Change the assigned user by id.
+    $new_user = $this->drupalCreateUser();
+    $entity->user->id = $new_user->uid;
+    $this->assertEqual($new_user->uid, $entity->user->id, 'Updated user id can be read.');
+    $this->assertEqual($new_user->name, $entity->user->entity->name, 'Updated user name value can be read.');
+
+    // Access the language property.
+    $this->assertEqual(LANGUAGE_NOT_SPECIFIED, $entity->language->langcode, 'Language code can be read.');
+    $this->assertEqual(language_load(LANGUAGE_NOT_SPECIFIED), $entity->language->object, 'Language object can be read.');
+
+    // Change the language by code.
+    $entity->language->langcode = language_default()->langcode;
+    $this->assertEqual(language_default()->langcode, $entity->language->langcode, 'Language code can be read.');
+    $this->assertEqual(language_default(), $entity->language->object, 'Language object can be read.');
+
+    // Revert language by code then try setting it by language object.
+    $entity->language->langcode = LANGUAGE_NOT_SPECIFIED;
+    $entity->language->object = language_default();
+    $this->assertEqual(language_default()->langcode, $entity->language->langcode, 'Language code can be read.');
+    $this->assertEqual(language_default(), $entity->language->object, 'Language object can be read.');
+
+    // Access the text field and test updating.
+    $this->assertEqual($entity->field_test_text->value, $this->entity_field_text, 'Text field can be read.');
+    $new_text = $this->randomName();
+    $entity->field_test_text->value = $new_text;
+    $this->assertEqual($entity->field_test_text->value, $new_text, 'Updated text field can be read.');
+
+    // Test creating the entity by passing in plain values.
+    $this->entity_name = $this->randomName();
+    $name_item[0]['value'] = $this->entity_name;
+    $this->entity_user = $this->drupalCreateUser();
+    $user_item[0]['id'] = $this->entity_user->uid;
+    $this->entity_field_text = $this->randomName();
+    $text_item[0]['value'] = $this->entity_field_text;
+
+    $entity = entity_create('entity_test', array(
+      'name' => $name_item,
+      'user' => $user_item,
+      'field_test_text' => $text_item,
+    ));
+    $this->assertEqual($this->entity_name, $entity->name->value, 'Name value can be read.');
+    $this->assertEqual($this->entity_user->uid, $entity->user->id, 'User id can be read.');
+    $this->assertEqual($this->entity_user->name, $entity->user->entity->name, 'User name can be read.');
+    $this->assertEqual($this->entity_field_text, $entity->field_test_text->value, 'Text field can be read.');
+  }
+
+  /**
+   * Tries to save and load an entity again.
+   */
+  public function testSave() {
+    $entity = $this->createTestEntity();
+    $entity->save();
+    $this->assertTrue((bool) $entity->id(), 'Entity has received an id.');
+
+    $entity = entity_load('entity_test', $entity->id());
+    $this->assertTrue((bool) $entity->id(), 'Entity loaded.');
+
+    // Access the name property.
+    $this->assertEqual(1, $entity->id->value, 'ID value can be read.');
+    $this->assertTrue(is_string($entity->uuid->value), 'UUID value can be read.');
+    $this->assertEqual(LANGUAGE_NOT_SPECIFIED, $entity->language->langcode, 'Language code can be read.');
+    $this->assertEqual(language_load(LANGUAGE_NOT_SPECIFIED), $entity->language->object, 'Language object can be read.');
+    $this->assertEqual($this->entity_user->uid, $entity->user->id, 'User id can be read.');
+    $this->assertEqual($this->entity_user->name, $entity->user->entity->name, 'User name can be read.');
+    $this->assertEqual($this->entity_field_text, $entity->field_test_text->value, 'Text field can be read.');
+  }
+
+  /**
+   * Tests introspection and getting metadata upfront.
+   */
+  public function testIntrospection() {
+    // Test getting metadata upfront, i.e. without having an entity object.
+    $definition = array(
+      'type' => 'entity',
+      'entity type' => 'entity_test',
+      'label' => t('Test entity'),
+    );
+    $property_entity = drupal_get_property($definition);
+    $definitions = $property_entity->getPropertyDefinitions($definition);
+    $this->assertEqual($definitions['name']['type'], 'string_item', 'Name property found.');
+    $this->assertEqual($definitions['user']['type'], 'entityreference_item', 'User property found.');
+    $this->assertEqual($definitions['field_test_text']['type'], 'text_item', 'Test-text-field property found.');
+
+    // Test introspecting an entity object.
+    // @todo: Add bundles and test bundles as well.
+    $entity = entity_create('entity_test', array());
+
+    $definitions = $entity->getPropertyDefinitions();
+    $this->assertEqual($definitions['name']['type'], 'string_item', 'Name property found.');
+    $this->assertEqual($definitions['user']['type'], 'entityreference_item', 'User property found.');
+    $this->assertEqual($definitions['field_test_text']['type'], 'text_item', 'Test-text-field property found.');
+
+    $name_properties = $entity->name->getPropertyDefinitions();
+    $this->assertEqual($name_properties['value']['type'], 'string', 'String value property of the name found.');
+
+    $userref_properties = $entity->user->getPropertyDefinitions();
+    $this->assertEqual($userref_properties['id']['type'], 'integer', 'Entity id property of the user found.');
+    $this->assertEqual($userref_properties['entity']['type'], 'entity', 'Entity reference property of the user found.');
+
+    $textfield_properties = $entity->field_test_text->getPropertyDefinitions();
+    $this->assertEqual($textfield_properties['value']['type'], 'string', 'String value property of the test-text field found.');
+    $this->assertEqual($textfield_properties['format']['type'], 'string', 'String format property of the test-text field found.');
+    $this->assertEqual($textfield_properties['processed']['type'], 'string', 'String processed property of the test-text field found.');
+
+    // @todo: Once the user entity has definitions, continue testing getting
+    // them from the $userref_values['entity'] property.
+  }
+
+  /**
+   * Tests iterating over properties.
+   */
+  public function testIterator() {
+    $entity = $this->createTestEntity();
+
+    foreach ($entity as $name => $property) {
+      $this->assertTrue($property instanceof EntityPropertyListInterface, "Property $name implements interface.");
+
+      foreach ($property as $delta => $item) {
+        $this->assertTrue($property[0] instanceof EntityPropertyItemInterface, "Item $delta of property $name implements interface.");
+
+        foreach ($item as $value_name => $value_property) {
+          $this->assertTrue($value_property instanceof DataItemInterface, "Value $value_name of item $delta of property $name implements interface.");
+
+          $value = $value_property->getValue();
+          $this->assertTrue(!isset($value) || is_scalar($value) || $value instanceof EntityInterface, "Value $value_name of item $delta of property $name is a primitive or an entity.");
+        }
+      }
+    }
+
+    $properties = $entity->getProperties();
+    $this->assertEqual(array_keys($properties), array_keys($entity->getPropertyDefinitions()), 'All properties returned.');
+    $this->assertEqual($properties, iterator_to_array($entity->getIterator()), 'Entity iterator iterates over all properties.');
+  }
+
+  /**
+   * Tests working with entity properties based upon data structure and data
+   * list interfaces.
+   */
+  public function testDataStructureInterfaces() {
+    $entity = $this->createTestEntity();
+    $entity->save();
+    $entity_definition = array(
+      'type' => 'entity',
+      'entity type' => 'entity_test',
+      'label' => t('Test entity'),
+    );
+    $property = drupal_get_property($entity_definition, $entity);
+
+    // For the test we navigate through the tree of contained properties and get
+    // all contained strings, limited by a certain depth.
+    $strings = array();
+    $this->getContainedStrings($property, 0, $strings);
+
+    // @todo: Once the user entity has defined properties this should contain
+    // the user name and other user entity strings as well.
+    $target_strings = array(
+      $entity->uuid->value,
+      LANGUAGE_NOT_SPECIFIED,
+      $this->entity_name,
+      $this->entity_field_text,
+      // Field format.
+      NULL,
+    );
+    $this->assertEqual($strings, $target_strings, 'All contained strings found.');
+  }
+
+  /**
+   * Recursive helper for getting all contained strings,
+   * i.e. properties of type string.
+   */
+  public function getContainedStrings(DataItemInterface $data_item, $depth, array &$strings) {
+
+    if ($data_item->getType() == 'string') {
+      $strings[] = $data_item->getValue();
+    }
+
+    // Recurse until a certain depth is reached if possible.
+    if ($depth < 7) {
+      if ($data_item instanceof \Drupal\Core\Data\DataListInterface) {
+        foreach ($data_item as $item) {
+          $this->getContainedStrings($item, $depth + 1, $strings);
+        }
+      }
+      elseif ($data_item instanceof \Drupal\Core\Data\DataStructureInterface) {
+        foreach ($data_item as $name => $property) {
+          $this->getContainedStrings($property, $depth + 1, $strings);
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests getting processed property values via a computed property.
+   */
+  public function testComputedProperties() {
+    // Make the test text field processed.
+    $instance = field_info_instance('entity_test', 'field_test_text', 'entity_test');
+    $instance['settings']['text_processing'] = 1;
+    field_update_instance($instance);
+
+    $entity = $this->createTestEntity();
+    $entity->field_test_text->value = "The <strong>text</strong> text to filter.";
+    $entity->field_test_text->format = filter_default_format();
+
+    $target = "<p>The &lt;strong&gt;text&lt;/strong&gt; text to filter.</p>\n";
+    $this->assertEqual($entity->field_test_text->processed, $target, 'Text is processed with the default filter.');
+
+    // Save and load entity and make sure it still works.
+    $entity->save();
+    $entity = entity_load('entity_test', $entity->id());
+    $this->assertEqual($entity->field_test_text->processed, $target, 'Text is processed with the default filter.');
+  }
+}
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php
index 9875b39..dbf4381 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityTranslationTest.php
@@ -83,25 +83,22 @@ class EntityTranslationTest extends WebTestBase {
     // Set the value in default language.
     $entity->set($this->field_name, array(0 => array('value' => 'default value')));
     // Get the value.
-    $value = $entity->get($this->field_name);
-    $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.');
+    $this->assertEqual($entity->get($this->field_name)->value, 'default value', 'Untranslated value retrieved.');
 
     // Set the value in a certain language. As the entity is not
     // language-specific it should use the default language and so ignore the
     // specified language.
     $entity->set($this->field_name, array(0 => array('value' => 'default value2')), $this->langcodes[1]);
-    $value = $entity->get($this->field_name);
-    $this->assertEqual($value, array(0 => array('value' => 'default value2')), 'Untranslated value updated.');
+    $this->assertEqual($entity->get($this->field_name)->value, 'default value2', 'Untranslated value updated.');
     $this->assertFalse($entity->translations(), 'No translations are available');
 
     // Test getting a field value using the default language for a not
     // language-specific entity.
-    $value = $entity->get($this->field_name, $this->langcodes[1]);
-    $this->assertEqual($value, array(0 => array('value' => 'default value2')), 'Untranslated value retrieved.');
+    $this->assertEqual($entity->get($this->field_name, $this->langcodes[1])->value, 'default value2', 'Untranslated value retrieved.');
 
     // Now, make the entity language-specific by assigning a language and test
     // translating it.
-    $entity->setLangcode($this->langcodes[0]);
+    $entity->language->object = $this->langcodes[0];
     $entity->{$this->field_name} = array();
     $this->assertEqual($entity->language(), language_load($this->langcodes[0]), 'Entity language retrieved.');
     $this->assertFalse($entity->translations(), 'No translations are available');
@@ -109,37 +106,36 @@ class EntityTranslationTest extends WebTestBase {
     // Set the value in default language.
     $entity->set($this->field_name, array(0 => array('value' => 'default value')));
     // Get the value.
-    $value = $entity->get($this->field_name);
-    $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value retrieved.');
+    $this->assertEqual($entity->get($this->field_name)->value, 'default value', 'Untranslated value retrieved.');
 
     // Set a translation.
     $entity->set($this->field_name, array(0 => array('value' => 'translation 1')), $this->langcodes[1]);
-    $value = $entity->get($this->field_name, $this->langcodes[1]);
-    $this->assertEqual($value, array(0 => array('value' => 'translation 1')), 'Translated value set.');
+    $this->assertEqual($entity->get($this->field_name, $this->langcodes[1])->value, 'translation 1', 'Translated value set.');
     // Make sure the untranslated value stays.
-    $value = $entity->get($this->field_name);
-    $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value stays.');
+    $this->assertEqual($entity->get($this->field_name)->value, 'default value', 'Untranslated value stays.');
 
     $translations[$this->langcodes[1]] = language_load($this->langcodes[1]);
     $this->assertEqual($entity->translations(), $translations, 'Translations retrieved.');
 
     // Try to get a not available translation.
-    $value = $entity->get($this->field_name, $this->langcodes[2]);
-    $this->assertNull($value, 'A translation that is not available is NULL.');
+    $this->assertNull($entity->get($this->field_name, $this->langcodes[2])->value, 'A translation that is not available is NULL.');
 
     // Try to get a value using an invalid language code.
-    $value = $entity->get($this->field_name, 'invalid');
-    $this->assertNull($value, 'A translation for an invalid language is NULL.');
-
+    try {
+      $entity->get($this->field_name, 'invalid')->value;
+      $this->fail('Getting a translation for an invalid language throws an exception.');
+    }
+    catch (InvalidArgumentException $e) {
+      $this->pass('A translation for an invalid language is NULL.');
+    }
     // Try to set a value using an invalid language code.
-    $message = "An exception is thrown when trying to set an invalid translation.";
     try {
       $entity->set($this->field_name, NULL, 'invalid');
       // This line is not expected to be executed unless something goes wrong.
-      $this->fail($message);
+      $this->fail("Setting a translation for an invalid language throws an exception.");
     }
-    catch (Exception $e) {
-      $this->assertTrue($e instanceof InvalidArgumentException, $message);
+    catch (InvalidArgumentException $e) {
+      $this->pass("Setting a translation for an invalid language throws an exception.");
     }
   }
 
@@ -153,29 +149,33 @@ class EntityTranslationTest extends WebTestBase {
 
     // Create a language neutral entity and check that properties are stored
     // as language neutral.
-    $entity = entity_create('entity_test', array('name' => $name, 'uid' => $uid));
+    $entity = entity_create('entity_test', array('name' => $name, 'user' => $uid));
     $entity->save();
     $entity = entity_test_load($entity->id());
     $this->assertEqual($entity->language()->langcode, LANGUAGE_NOT_SPECIFIED, 'Entity created as language neutral.');
-    $this->assertEqual($name, $entity->get('name', LANGUAGE_NOT_SPECIFIED), 'The entity name has been correctly stored as language neutral.');
-    $this->assertEqual($uid, $entity->get('uid', LANGUAGE_NOT_SPECIFIED), 'The entity author has been correctly stored as language neutral.');
-    $this->assertNull($entity->get('name', $langcode), 'The entity name is not available as a language-aware property.');
-    $this->assertNull($entity->get('uid', $langcode), 'The entity author is not available as a language-aware property.');
-    $this->assertEqual($name, $entity->get('name'), 'The entity name can be retrieved without specifying a language.');
-    $this->assertEqual($uid, $entity->get('uid'), 'The entity author can be retrieved without specifying a language.');
+    $this->assertEqual($name, $entity->get('name', LANGUAGE_NOT_SPECIFIED)->value, 'The entity name has been correctly stored as language neutral.');
+    $this->assertEqual($uid, $entity->get('user', LANGUAGE_NOT_SPECIFIED)->id, 'The entity author has been correctly stored as language neutral.');
+    // As fields, translatable properties should ignore the given langcode and
+    // use neutral language if the entity is not translatable.
+    $this->assertEqual($name, $entity->get('name', $langcode)->value, 'The entity name defaults to neutral language.');
+    $this->assertEqual($uid, $entity->get('user', $langcode)->id, 'The entity author defaults to neutral language.');
+    $this->assertEqual($name, $entity->get('name')->value, 'The entity name can be retrieved without specifying a language.');
+    $this->assertEqual($uid, $entity->get('user')->id, 'The entity author can be retrieved without specifying a language.');
 
     // Create a language-aware entity and check that properties are stored
     // as language-aware.
-    $entity = entity_create('entity_test', array('name' => $name, 'uid' => $uid, 'langcode' => $langcode));
+    $entity = entity_create('entity_test', array('name' => $name, 'user' => $uid, 'language' => $langcode));
     $entity->save();
     $entity = entity_test_load($entity->id());
     $this->assertEqual($entity->language()->langcode, $langcode, 'Entity created as language specific.');
-    $this->assertEqual($name, $entity->get('name', $langcode), 'The entity name has been correctly stored as a language-aware property.');
-    $this->assertEqual($uid, $entity->get('uid', $langcode), 'The entity author has been correctly stored as a language-aware property.');
-    $this->assertNull($entity->get('name', LANGUAGE_NOT_SPECIFIED), 'The entity name is not available as a language neutral property.');
-    $this->assertNull($entity->get('uid', LANGUAGE_NOT_SPECIFIED), 'The entity author is not available as a language neutral property.');
-    $this->assertEqual($name, $entity->get('name'), 'The entity name can be retrieved without specifying a language.');
-    $this->assertEqual($uid, $entity->get('uid'), 'The entity author can be retrieved without specifying a language.');
+    $this->assertEqual($name, $entity->get('name', $langcode)->value, 'The entity name has been correctly stored as a language-aware property.');
+    $this->assertEqual($uid, $entity->get('user', $langcode)->id, 'The entity author has been correctly stored as a language-aware property.');
+    // Translatable properties on a translatable entity should use default
+    // language if LANGUAGE_NOT_SPECIFIED is passed.
+    $this->assertEqual($name, $entity->get('name', LANGUAGE_NOT_SPECIFIED)->value, 'The entity name defaults to the default language.');
+    $this->assertEqual($uid, $entity->get('user', LANGUAGE_NOT_SPECIFIED)->id, 'The entity author defaults to the default language.');
+    $this->assertEqual($name, $entity->get('name')->value, 'The entity name can be retrieved without specifying a language.');
+    $this->assertEqual($uid, $entity->get('user')->id, 'The entity author can be retrieved without specifying a language.');
 
     // Create property translations.
     $properties = array();
@@ -183,17 +183,17 @@ class EntityTranslationTest extends WebTestBase {
     foreach ($this->langcodes as $langcode) {
       if ($langcode != $default_langcode) {
         $properties[$langcode] = array(
-          'name' => $this->randomName(),
-          'uid' => mt_rand(0, 127),
+          'name' => array(0 => $this->randomName()),
+          'user' => array(0 => mt_rand(0, 127)),
         );
       }
       else {
         $properties[$langcode] = array(
-          'name' => $name,
-          'uid' => $uid,
+          'name' => array(0 => $name),
+          'user' => array(0 => $uid),
         );
       }
-      $entity->setProperties($properties[$langcode], $langcode);
+      $entity->getTranslation($langcode)->setProperties($properties[$langcode]);
     }
     $entity->save();
 
@@ -201,8 +201,8 @@ class EntityTranslationTest extends WebTestBase {
     $entity = entity_test_load($entity->id());
     foreach ($this->langcodes as $langcode) {
       $args = array('%langcode' => $langcode);
-      $this->assertEqual($properties[$langcode]['name'], $entity->get('name', $langcode), format_string('The entity name has been correctly stored for language %langcode.', $args));
-      $this->assertEqual($properties[$langcode]['uid'], $entity->get('uid', $langcode), format_string('The entity author has been correctly stored for language %langcode.', $args));
+      $this->assertEqual($properties[$langcode]['name'][0], $entity->get('name', $langcode)->value, format_string('The entity name has been correctly stored for language %langcode.', $args));
+      $this->assertEqual($properties[$langcode]['user'][0], $entity->get('user', $langcode)->id, format_string('The entity author has been correctly stored for language %langcode.', $args));
     }
 
     // Test query conditions (cache is reset at each call).
@@ -210,7 +210,11 @@ class EntityTranslationTest extends WebTestBase {
     // Create an additional entity with only the uid set. The uid for the
     // original language is the same of one used for a translation.
     $langcode = $this->langcodes[1];
-    entity_create('entity_test', array('uid' => $properties[$langcode]['uid']))->save();
+    entity_create('entity_test', array(
+      'user' => $properties[$langcode]['user'],
+      'name' => 'some name',
+    ))->save();
+
     $entities = entity_test_load_multiple(FALSE, array(), TRUE);
     $this->assertEqual(count($entities), 3, 'Three entities were created.');
     $entities = entity_test_load_multiple(array($translated_id), array(), TRUE);
@@ -219,15 +223,15 @@ class EntityTranslationTest extends WebTestBase {
     $this->assertEqual(count($entities), 2, 'Two entities correctly loaded by name.');
     // @todo The default language condition should go away in favor of an
     // explicit parameter.
-    $entities = entity_test_load_multiple(array(), array('name' => $properties[$langcode]['name'], 'default_langcode' => 0), TRUE);
+    $entities = entity_test_load_multiple(array(), array('name' => $properties[$langcode]['name'][0], 'default_langcode' => 0), TRUE);
     $this->assertEqual(count($entities), 1, 'One entity correctly loaded by name translation.');
     $entities = entity_test_load_multiple(array(), array('langcode' => $default_langcode, 'name' => $name), TRUE);
     $this->assertEqual(count($entities), 1, 'One entity correctly loaded by name and language.');
-    $entities = entity_test_load_multiple(array(), array('langcode' => $langcode, 'name' => $properties[$langcode]['name']), TRUE);
+    $entities = entity_test_load_multiple(array(), array('langcode' => $langcode, 'name' => $properties[$langcode]['name'][0]), TRUE);
     $this->assertEqual(count($entities), 0, 'No entity loaded by name translation specifying the translation language.');
-    $entities = entity_test_load_multiple(array(), array('langcode' => $langcode, 'name' => $properties[$langcode]['name'], 'default_langcode' => 0), TRUE);
+    $entities = entity_test_load_multiple(array(), array('langcode' => $langcode, 'name' => $properties[$langcode]['name'][0], 'default_langcode' => 0), TRUE);
     $this->assertEqual(count($entities), 1, 'One entity loaded by name translation and language specifying to look for translations.');
-    $entities = entity_test_load_multiple(array(), array('uid' => $properties[$langcode]['uid'], 'default_langcode' => NULL), TRUE);
+    $entities = entity_test_load_multiple(array(), array('uid' => $properties[$langcode]['user'][0], 'default_langcode' => NULL), TRUE);
     $this->assertEqual(count($entities), 2, 'Two entities loaded by uid without caring about property translatability.');
   }
 }
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php
index 75333b4..dca4b1b 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityUUIDTest.php
@@ -41,35 +41,35 @@ class EntityUUIDTest extends WebTestBase {
       'name' => $this->randomName(),
       'uuid' => $uuid,
     ));
-    $this->assertIdentical($custom_entity->get('uuid'), $uuid);
+    $this->assertIdentical($custom_entity->uuid->value, $uuid);
     // Save this entity, so we have more than one later.
     $custom_entity->save();
 
     // Verify that a new UUID is generated upon creating an entity.
     $entity = entity_create('entity_test', array('name' => $this->randomName()));
-    $uuid = $entity->get('uuid');
+    $uuid = $entity->uuid->value;
     $this->assertTrue($uuid);
 
     // Verify that the new UUID is different.
-    $this->assertNotEqual($custom_entity->get('uuid'), $uuid);
+    $this->assertNotEqual($custom_entity->uuid->value, $uuid);
 
     // Verify that the UUID is retained upon saving.
     $entity->save();
-    $this->assertIdentical($entity->get('uuid'), $uuid);
+    $this->assertIdentical($entity->uuid->value, $uuid);
 
     // Verify that the UUID is retained upon loading.
     $entity_loaded = entity_test_load($entity->id(), TRUE);
-    $this->assertIdentical($entity_loaded->get('uuid'), $uuid);
+    $this->assertIdentical($entity_loaded->uuid->value, $uuid);
 
     // Verify that entity_load_by_uuid() loads the same entity.
     $entity_loaded_by_uuid = entity_load_by_uuid('entity_test', $uuid, TRUE);
-    $this->assertIdentical($entity_loaded_by_uuid->get('uuid'), $uuid);
+    $this->assertIdentical($entity_loaded_by_uuid->uuid->value, $uuid);
     $this->assertEqual($entity_loaded_by_uuid, $entity_loaded);
 
     // Creating a duplicate needs to result in a new UUID.
     $entity_duplicate = $entity->createDuplicate();
-    $this->assertNotEqual($entity_duplicate->get('uuid'), $entity->get('uuid'));
-    $this->assertNotNull($entity_duplicate->get('uuid'));
+    $this->assertNotEqual($entity_duplicate->uuid->value, $entity->uuid->value);
+    $this->assertNotNull($entity_duplicate->uuid->value);
     $entity_duplicate->save();
     $this->assertNotEqual($entity->id(), $entity_duplicate->id());
   }
diff --git a/core/modules/entity/tests/modules/entity_test/entity_test.info b/core/modules/entity/tests/modules/entity_test/entity_test.info
index ae6006b..6458417 100644
--- a/core/modules/entity/tests/modules/entity_test/entity_test.info
+++ b/core/modules/entity/tests/modules/entity_test/entity_test.info
@@ -4,4 +4,5 @@ package = Testing
 version = VERSION
 core = 8.x
 dependencies[] = entity
+dependencies[] = field
 hidden = TRUE
diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
index 6c95f75..b092236 100644
--- a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
+++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
@@ -7,137 +7,52 @@
 
 namespace Drupal\entity_test;
 
-use InvalidArgumentException;
-
-use Drupal\entity\Entity;
+use Drupal\entity\EntityNG;
 
 /**
  * Defines the test entity class.
  */
-class EntityTest extends Entity {
+class EntityTest extends EntityNG {
 
   /**
-   * An array keyed by language code where the entity properties are stored.
+   * The entity ID.
    *
-   * @var array
+   * @var \Drupal\entity\EntityPropertyListInterface
    */
-  protected $properties;
+  public $id;
 
   /**
-   * An array of allowed language codes.
+   * The entity UUID.
    *
-   * @var array
-   */
-  protected static $langcodes;
-
-  /**
-   * Constructs a new entity object.
+   * @var \Drupal\entity\EntityPropertyListInterface
    */
-  public function __construct(array $values, $entity_type) {
-    parent::__construct($values, $entity_type);
-
-    if (!isset(self::$langcodes)) {
-      // The allowed languages are simply all the available ones in the system.
-      self::$langcodes = drupal_map_assoc(array_keys(language_list(LANGUAGE_ALL)));
-    }
-
-    // Initialize the original entity language with the provided value or fall
-    // back to LANGUAGE_NOT_SPECIFIED if none was specified. We do not check
-    // against allowed languages here, since throwing an exception would make an
-    // entity created in a subsequently uninstalled language not instantiable.
-    $this->langcode = !empty($values['langcode']) ? $values['langcode'] : LANGUAGE_NOT_SPECIFIED;
-
-    // Set initial values ensuring that only real properties are stored.
-    // @todo For now we have no way to mark a property as multlingual hence we
-    // just assume that all of them are.
-    unset($values['id'], $values['uuid'], $values['default_langcode']);
-    $this->setProperties($values, $this->langcode);
-  }
+  public $uuid;
 
   /**
-   * Sets the entity original langcode.
+   * The name of the test entity.
    *
-   * @param $langcode
-   */
-  public function setLangcode($langcode) {
-    // If the original language is changed the related properties must change
-    // their language accordingly.
-    $prev_langcode = $this->langcode;
-    if (isset($this->properties[$prev_langcode])) {
-      $this->properties[$langcode] = $this->properties[$prev_langcode];
-      unset($this->properties[$prev_langcode]);
-    }
-    $this->langcode = $langcode;
-  }
-
-  /**
-   * Overrides EntityInterface::get().
+   * @var \Drupal\entity\EntityPropertyListInterface
    */
-  public function get($property_name, $langcode = NULL) {
-    $langcode = !empty($langcode) ? $langcode : $this->langcode;
-    $entity_info = $this->entityInfo();
-    if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
-      return parent::get($property_name, $langcode);
-    }
-    elseif (isset($this->properties[$langcode][$property_name])) {
-      return $this->properties[$langcode][$property_name];
-    }
-    else {
-      // @todo Remove this. All properties should be stored in the $properties
-      // array once we have a Property API in place.
-      return property_exists($this, $property_name) ? $this->{$property_name} : NULL;
-    }
-  }
-
-  /**
-   * Overrides EntityInterface::set().
-   */
-  public function set($property_name, $value, $langcode = NULL) {
-    $langcode = !empty($langcode) ? $langcode : $this->langcode;
-    if (!isset(self::$langcodes[$langcode])) {
-      throw new InvalidArgumentException("Detected an invalid language '$langcode' while setting '$property_name' to '$value'.");
-    }
-    $entity_info = $this->entityInfo();
-    if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
-      parent::set($property_name, $value, $langcode);
-    }
-    else {
-      $this->properties[$langcode][$property_name] = $value;
-    }
-  }
-
-  /**
-   * Overrides EntityInterface::translations().
-   */
-  public function translations() {
-    $translations = !empty($this->properties) ? $this->properties : array();
-    $languages = array_intersect_key(self::$langcodes, $translations);
-    unset($languages[$this->langcode]);
-    return $languages + parent::translations();
-  }
+  public $name;
 
   /**
-   * Returns the property array for the given language.
+   * The associated user.
    *
-   * @param string $langcode
-   *   (optional) The language code to be used to retrieve the properties.
+   * @var \Drupal\entity\EntityPropertyListInterface
    */
-  public function getProperties($langcode = NULL) {
-    $langcode = !empty($langcode) ? $langcode : $this->langcode;
-    return isset($this->properties[$langcode]) ? $this->properties[$langcode] : array();
-  }
+  public $user;
 
   /**
-   * Sets the property array for the given language.
-   *
-   * @param array $properties
-   *   A keyed array of properties to be set with their 'langcode' as one of the
-   *   keys. If no language is provided the entity language is used.
-   * @param string $langcode
-   *   (optional) The language code to be used to set the properties.
+   * Overrides Entity::__construct().
    */
-  public function setProperties(array $properties, $langcode = NULL) {
-    $langcode = !empty($langcode) ? $langcode : $this->langcode;
-    $this->properties[$langcode] = $properties;
+  public function __construct(array $values, $entity_type) {
+    parent::__construct($values, $entity_type);
+
+    // We unset all defined properties, so magic getters apply.
+    unset($this->id);
+    unset($this->langcode);
+    unset($this->uuid);
+    unset($this->name);
+    unset($this->user);
   }
 }
diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
index 147f030..61ff9f0 100644
--- a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
+++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
@@ -11,6 +11,8 @@ use PDO;
 
 use Drupal\entity\EntityInterface;
 use Drupal\entity\DatabaseStorageController;
+use Drupal\entity\EntityStorageException;
+use Drupal\Component\Uuid\Uuid;
 
 /**
  * Defines the controller class for the test entity.
@@ -21,6 +23,93 @@ use Drupal\entity\DatabaseStorageController;
 class EntityTestStorageController extends DatabaseStorageController {
 
   /**
+   * The entity class to use.
+   *
+   * @todo: Remove this once this is moved in the main controller.
+   *
+   * @var string
+   */
+  protected $entityClass;
+
+  /**
+   * The entity bundle key.
+   *
+   * @var string|bool
+   */
+  protected $bundleKey;
+
+  /**
+   * Overrides DatabaseStorageController::__construct().
+   */
+  public function __construct($entityType) {
+    parent::__construct($entityType);
+    $this->bundleKey = !empty($this->entityInfo['entity keys']['bundle']) ? $this->entityInfo['entity keys']['bundle'] : FALSE;
+
+    // Let load() get stdClass storage records. We map them to entities in
+    // attachLoad().
+    // @todo: Remove this once this is moved in the main controller.
+    $this->entityClass = $this->entityInfo['entity class'];
+    unset($this->entityInfo['entity class']);
+  }
+
+  /**
+   * Overrides DatabaseStorageController::create().
+   *
+   * @param array $values
+   *   An array of values to set, keyed by property name. The value has to be
+   *   the plain value of an entity property, i.e. an array of property items.
+   *   If no array is given, the value will be set for the first property item.
+   *   Thus to set the first item of a 'name' property one can pass:
+   *   @code
+   *     $values = array('name' => array(0 => array('value' => 'the name')));
+   *   @endcode
+   *   or
+   *   @code
+   *     $values = array('name' => array('value' => 'the name'));
+   *   @endcode
+   *
+   *   Furthermore, property items having only a single value support setting
+   *   this value without passing an array of values, making it possible to
+   *   set the 'name' property via:
+   *   @code
+   *     $values = array('name' => 'the name');
+   *   @endcode
+   *
+   * @todo: Remove this once this is moved in the main controller.
+   */
+  public function create(array $values) {
+    // Pass in default values.
+    $defaults = array();
+    $defaults['language'][LANGUAGE_NOT_SPECIFIED][0]['langcode'] = LANGUAGE_NOT_SPECIFIED;
+
+    $entity = new $this->entityClass(array('values' => $defaults), $this->entityType);
+
+    // Make sure to set the bundle first.
+    if ($this->bundleKey) {
+      $entity->{$this->bundleKey} = $values[$this->bundleKey];
+      unset($values[$this->bundleKey]);
+    }
+
+    // Set all other given values.
+    foreach ($values as $name => $value) {
+      if (is_array($value) && is_numeric(current(array_keys($value)))) {
+        $entity->$name = $value;
+      }
+      else {
+        // Support passing in the first value of a property item.
+        $entity->{$name}[0] = $value;
+      }
+    }
+
+    // Assign a new UUID if there is none yet.
+    if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
+      $uuid = new Uuid();
+      $entity->{$this->uuidKey}->value = $uuid->generate();
+    }
+    return $entity;
+  }
+
+  /**
    * Overrides Drupal\entity\DatabaseStorageController::buildQuery().
    */
   protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
@@ -49,7 +138,7 @@ class EntityTestStorageController extends DatabaseStorageController {
       if (!array_key_exists('default_langcode', $conditions)) {
         $conditions['default_langcode'] = 1;
       }
-      // If the 'default_langcode' flag is esplicitly not set, we do not care
+      // If the 'default_langcode' flag is explicitly not set, we do not care
       // whether the queried values are in the original entity language or not.
       elseif ($conditions['default_langcode'] === NULL) {
         unset($conditions['default_langcode']);
@@ -64,9 +153,51 @@ class EntityTestStorageController extends DatabaseStorageController {
   }
 
   /**
-   * Overrides Drupal\entity\DatabaseStorageController::attachLoad().
+   * Overrides DatabaseStorageController::attachLoad().
+   *
+   * Added mapping from storage records to entities.
    */
   protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
+    // Now map the record values to the according entity properties and
+    // activate compatibility mode.
+    $queried_entities = $this->mapFromStorageRecords($queried_entities);
+
+    // Load data of translatable properties.
+    $this->attachPropertyData($queried_entities);
+
+    parent::attachLoad($queried_entities, $revision_id);
+
+    // Loading is finished, so disable compatibility mode now.
+    foreach ($queried_entities as $entity) {
+      $entity->setCompatibilityMode(FALSE);
+    }
+  }
+
+  /**
+   * Maps from storage records to entity objects.
+   *
+   * @return array
+   *   An array of entity objects implementing the EntityInterface.
+   */
+  protected function mapFromStorageRecords(array $records) {
+
+    foreach ($records as $id => $record) {
+      $entity = new $this->entityClass(array(), $this->entityType);
+      $entity->setCompatibilityMode(TRUE);
+
+      $entity->id[LANGUAGE_NOT_SPECIFIED][0]['value'] = $id;
+      $entity->uuid[LANGUAGE_NOT_SPECIFIED][0]['value'] = $record->uuid;
+      $entity->language[LANGUAGE_NOT_SPECIFIED][0]['langcode'] = $record->langcode;
+
+      $records[$id] = $entity;
+    }
+    return $records;
+  }
+
+  /**
+   * Attaches property data in all languages for translatable properties.
+   */
+  protected function attachPropertyData(&$queried_entities) {
     $data = db_select('entity_test_property_data', 'data', array('fetch' => PDO::FETCH_ASSOC))
       ->fields('data')
       ->condition('id', array_keys($queried_entities))
@@ -74,35 +205,121 @@ class EntityTestStorageController extends DatabaseStorageController {
       ->execute();
 
     foreach ($data as $values) {
-      $entity = $queried_entities[$values['id']];
-      $langcode = $values['langcode'];
-      if (!empty($values['default_langcode'])) {
-        $entity->setLangcode($langcode);
+      $id = $values['id'];
+      // Property values in default language are stored with
+      // LANGUAGE_NOT_SPECIFIED as key.
+      $langcode = empty($values['default_langcode']) ? $values['langcode'] : LANGUAGE_NOT_SPECIFIED;
+
+      $queried_entities[$id]->name[$langcode][0]['value'] = $values['name'];
+      $queried_entities[$id]->user[$langcode][0]['id'] = $values['uid'];
+    }
+  }
+
+  /**
+   * Overrides DatabaseStorageController::save().
+   *
+   * Added mapping from entities to storage records before saving.
+   */
+  public function save(EntityInterface $entity) {
+    $transaction = db_transaction();
+    try {
+      // Load the stored entity, if any.
+      if (!$entity->isNew() && !isset($entity->original)) {
+        $entity->original = entity_load_unchanged($this->entityType, $entity->id());
+      }
+
+      $this->preSave($entity);
+      $this->invokeHook('presave', $entity);
+
+      // Create the storage record to be saved.
+      $record = $this->maptoStorageRecord($entity);
+      // Update the original values so that the compatibility mode works with
+      // the update values, what is required by field API attachers.
+      // @todo Once field API has been converted to use the Property API, move
+      // this after insert/update hooks.
+      $entity->updateOriginalValues();
+
+      if (!$entity->isNew()) {
+        $return = drupal_write_record($this->entityInfo['base table'], $record, 'id');
+        $this->resetCache(array($entity->id()));
+        $this->postSave($entity, TRUE);
+        $this->invokeHook('update', $entity);
+      }
+      else {
+        $return = drupal_write_record($this->entityInfo['base table'], $record);
+        // Reset general caches, but keep caches specific to certain entities.
+        $this->resetCache(array());
+
+        $entity->{$this->idKey}->value = $record->id;
+        $entity->enforceIsNew(FALSE);
+        $this->postSave($entity, FALSE);
+        $this->invokeHook('insert', $entity);
       }
-      // Make sure only real properties are stored.
-      unset($values['id'], $values['default_langcode']);
-      $entity->setProperties($values, $langcode);
+
+      // Ignore slave server temporarily.
+      db_ignore_slave();
+      unset($entity->original);
+
+      return $return;
     }
+    catch (Exception $e) {
+      $transaction->rollback();
+      watchdog_exception($this->entityType, $e);
+      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+    }
+  }
 
-    parent::attachLoad($queried_entities, $revision_id);
+  /**
+   * Overrides DatabaseStorageController::invokeHook().
+   *
+   * Invokes field API attachers in compatibility mode and disables it
+   * afterwards.
+   */
+  protected function invokeHook($hook, EntityInterface $entity) {
+    if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) {
+      $entity->setCompatibilityMode(TRUE);
+      $function($this->entityType, $entity);
+      $entity->setCompatibilityMode(FALSE);
+    }
+
+    // Invoke the hook.
+    module_invoke_all($this->entityType . '_' . $hook, $entity);
+    // Invoke the respective entity-level hook.
+    module_invoke_all('entity_' . $hook, $entity, $this->entityType);
+  }
+
+  /**
+   * Maps from an entity object to the storage record of the base table.
+   */
+  protected function mapToStorageRecord(EntityInterface $entity) {
+    $record = new \stdClass();
+    $record->id = $entity->id();
+    $record->langcode = $entity->language->langcode;
+    $record->uuid = $entity->uuid->value;
+    return $record;
   }
 
   /**
    * Overrides Drupal\entity\DatabaseStorageController::postSave().
+   *
+   * Stores values of translatable properties.
    */
   protected function postSave(EntityInterface $entity, $update) {
-    $default_langcode = ($language = $entity->language()) ? $language->langcode : LANGUAGE_NOT_SPECIFIED;
     $langcodes = array_keys($entity->translations());
-    $langcodes[] = $default_langcode;
+    // Also add values in default language, which are keyed by
+    // LANGUAGE_NOT_SPECIFIED.
+    $langcodes[] = LANGUAGE_NOT_SPECIFIED;
 
     foreach ($langcodes as $langcode) {
-      $properties = $entity->getProperties($langcode);
+      $translation = $entity->getTranslation($langcode);
 
       $values = array(
         'id' => $entity->id(),
-        'langcode' => $langcode,
-        'default_langcode' => intval($default_langcode == $langcode),
-      ) + $properties;
+        'langcode' => LANGUAGE_NOT_SPECIFIED == $langcode ? $entity->language->langcode : $langcode,
+        'default_langcode' => intval(LANGUAGE_NOT_SPECIFIED == $langcode),
+        'name' => $translation->name->value,
+        'uid' => $translation->user->id,
+      );
 
       db_merge('entity_test_property_data')
         ->fields($values)
@@ -120,4 +337,67 @@ class EntityTestStorageController extends DatabaseStorageController {
       ->condition('id', array_keys($entities))
       ->execute();
   }
+
+  /**
+   * Overrides \Drupal\entity\DataBaseStorageController::basePropertyDefinitions().
+   */
+  public function basePropertyDefinitions() {
+    $properties['id'] = array(
+      'label' => t('ID'),
+      'description' => ('The ID of the test entity.'),
+      'type' => 'integer_item',
+      'list' => TRUE,
+    );
+    $properties['uuid'] = array(
+      'label' => t('UUID'),
+      'description' => ('The UUID of the test entity.'),
+      'type' => 'string_item',
+      'list' => TRUE,
+    );
+    $properties['language'] = array(
+      'label' => t('Language'),
+      'description' => ('The language of the test entity.'),
+      'type' => 'language_item',
+      'list' => TRUE,
+    );
+    $properties['name'] = array(
+      'label' => t('Name'),
+      'description' => ('The name of the test entity.'),
+      'type' => 'string_item',
+      'list' => TRUE,
+      'translatable' => TRUE,
+    );
+    $properties['user'] = array(
+      'label' => t('User'),
+      'description' => t('The associated user.'),
+      'type' => 'entityreference_item',
+      'entity type' => 'user',
+      'list' => TRUE,
+      'translatable' => TRUE,
+    );
+    return $properties;
+  }
+
+  /**
+   * Overrides \Drupal\entity\DataBaseStorageController::cacheGet().
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    $entities = parent::cacheGet($ids, array());
+
+    // Exclude any entities loaded from cache if they don't match $conditions.
+    // This ensures the same behavior whether loading from memory or database.
+    if ($conditions) {
+      if (!$ids) {
+        $entities = $this->entityCache;
+      }
+
+      foreach ($entities as $entity) {
+        $entity_values = $entity->toArray();
+        if (array_diff_assoc($conditions, $entity_values)) {
+          unset($entities[$entity->id()]);
+        }
+      }
+    }
+    return $entities;
+  }
 }
diff --git a/core/modules/field/field.default.inc b/core/modules/field/field.default.inc
index b4a6f50..bb7f8d6 100644
--- a/core/modules/field/field.default.inc
+++ b/core/modules/field/field.default.inc
@@ -107,7 +107,7 @@ function field_default_insert($entity_type, $entity, $field, $instance, $langcod
   // assigning it a default value. This way we ensure that only the intended
   // languages get a default value. Otherwise we could have default values for
   // not yet open languages.
-  if (empty($entity) || !property_exists($entity, $field['field_name']) ||
+  if (empty($entity) || (!isset($entity->{$field['field_name']}[$langcode]) && !property_exists($entity, $field['field_name'])) ||
     (isset($entity->{$field['field_name']}[$langcode]) && count($entity->{$field['field_name']}[$langcode]) == 0)) {
     $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode);
   }
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 8157971..eae7dee 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -369,6 +369,66 @@ function field_system_info_alter(&$info, $file, $type) {
 }
 
 /**
+ * Implements hook_data_type_info() to register data types for all field types.
+ */
+function field_data_type_info() {
+  $field_types = field_info_field_types();
+  $items = array();
+
+  // Expose data types for all the field type items.
+  // @todo: Make 'property class' mandatory.
+  foreach ($field_types as $type_name => $type_info) {
+
+    if (!empty($type_info['property class'])) {
+      $items[$type_name . '_item'] = array(
+        'label' => t('Field !label item', array('!label' => $type_info['label'])),
+        'class' => $type_info['property class'],
+        'list class' => !empty($type_info['property list class']) ? $type_info['property list class'] : '\Drupal\entity\Property\EntityPropertyList',
+      );
+    }
+  }
+  return $items;
+}
+
+/**
+ * Implements hook_entity_property_info() to define properties for all fields.
+ */
+function field_entity_property_info($entity_type) {
+  $property_info = array();
+  $field_types = field_info_field_types();
+
+  foreach (field_info_instances($entity_type) as $bundle_name => $instances) {
+    $optional = $bundle_name != $entity_type;
+
+    foreach ($instances as $field_name => $instance) {
+      $field = field_info_field($field_name);
+
+      if (!empty($field_types[$field['type']]['property class'])) {
+
+        // @todo: Allow for adding field type settings.
+        $definition = array(
+          'label' => t('Field !name', array('!name' => $field_name)),
+          'type' => $field['type'] . '_item',
+          'field' => $field_name,
+          'list' => TRUE,
+          'translatable' => !empty($field['translatable'])
+        );
+
+        if ($optional) {
+          $property_info['optional'][$field_name] = $definition;
+          $property_info['bundle map'][$bundle_name][] = $field_name;
+        }
+        else {
+          $property_info['definitions'][$field_name] = $definition;
+        }
+      }
+    }
+  }
+
+  return $property_info;
+}
+
+/**
  * Applies language fallback rules to the fields attached to the given entity.
  *
  * Core language fallback rules simply check if fields have a field translation
diff --git a/core/modules/field/modules/text/lib/Drupal/text/PropertyProcessedText.php b/core/modules/field/modules/text/lib/Drupal/text/PropertyProcessedText.php
new file mode 100644
index 0000000..aa1d276
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/PropertyProcessedText.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\text\PropertyProcessedText.
+ */
+
+namespace Drupal\text;
+use Drupal\Core\Data\DataItemInterface;
+use Drupal\Core\Data\DataReadOnlyException;
+
+/**
+ * The string property type.
+ */
+class PropertyProcessedText extends \Drupal\Core\Data\Type\String {
+
+  /**
+   * The text property.
+   *
+   * @var \Drupal\Core\Data\DataItemInterface
+   */
+  protected $text;
+
+  /**
+   * The text format property.
+   *
+   * @var \Drupal\Core\Data\DataItemInterface
+   */
+  protected $format;
+
+  /**
+   * Implements DataItemInterface::__construct().
+   */
+  public function __construct(array $definition, $value = NULL, $context = array()) {
+    $this->definition = $definition;
+
+    if (!isset($context['parent'])) {
+      throw new \InvalidArgumentException('Computed properties require context for computation.');
+    }
+    if (!isset($definition['source'])) {
+      throw new \InvalidArgumentException("The definition's 'source' key has to specify the name of the text property to be processed.");
+    }
+
+    $this->text = $context['parent']->get($definition['source']);
+    $this->format = $context['parent']->get('format');
+
+  }
+
+  /**
+   * Implements DataItemInterface::getValue().
+   */
+  public function getValue($langcode = NULL) {
+    // @todo: Determine a way to get the field $instance here.
+    // Either implement per-bundle property definition overrides or pass on
+    // entity-context (entity type, bundle, property name). For now, we assume
+    // text processing is enabled if a format is given.
+    if ($this->format->value) {
+      return check_markup($this->text->value, $this->format->value, $langcode);
+    }
+    else {
+      // If no format is available, still make sure to sanitize the text.
+      return check_plain($this->text->value);
+    }
+  }
+
+  /**
+   * Implements DataItemInterface::setValue().
+   */
+  public function setValue($value) {
+    if (isset($value)) {
+      throw new DataReadOnlyException('Unable to set a computed property.');
+    }
+  }
+}
diff --git a/core/modules/field/modules/text/lib/Drupal/text/PropertyTextItem.php b/core/modules/field/modules/text/lib/Drupal/text/PropertyTextItem.php
new file mode 100644
index 0000000..7faa0f6
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/PropertyTextItem.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\text\PropertyTextItem.
+ */
+
+namespace Drupal\text;
+use \Drupal\entity\Property\EntityPropertyItemBase;
+
+/**
+ * Defines the 'text_item' and 'text_long_item' entity property items.
+ */
+class PropertyTextItem extends EntityPropertyItemBase {
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // Statically cache the definitions to avoid creating lots of array copies.
+    $definitions = drupal_static(__CLASS__);
+
+    if (!isset($definitions)) {
+      $definitions['value'] = array(
+        'type' => 'string',
+        'label' => t('Text value'),
+      );
+      $definitions['format'] = array(
+        'type' => 'string',
+        'label' => t('Text format'),
+      );
+      $definitions['processed'] = array(
+        'type' => 'string',
+        'label' => t('Processed text'),
+        'description' => t('The text value with the text format applied.'),
+        'html' => TRUE,
+        'computed' => TRUE,
+        'class' => '\Drupal\text\PropertyProcessedText',
+        'source' => 'value',
+      );
+    }
+    return $definitions;
+  }
+}
+
diff --git a/core/modules/field/modules/text/lib/Drupal/text/PropertyTextWithSummaryItem.php b/core/modules/field/modules/text/lib/Drupal/text/PropertyTextWithSummaryItem.php
new file mode 100644
index 0000000..4ed7f4f
--- /dev/null
+++ b/core/modules/field/modules/text/lib/Drupal/text/PropertyTextWithSummaryItem.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\text\PropertyTextWithSummaryItem.
+ */
+
+namespace Drupal\text;
+use \Drupal\text\PropertyTextItem;
+
+/**
+ * Defines the 'text_with_summary' entity property item.
+ */
+class PropertyTextWithSummaryItem extends PropertyTextItem {
+
+  /**
+   * Implements DataStructureInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // Statically cache the definitions to avoid creating lots of array copies.
+    $definitions = drupal_static(__CLASS__);
+
+    if (!isset($definitions)) {
+
+      $definitions = parent::getPropertyDefinitions();
+
+      $definitions['summary'] = array(
+        'type' => 'string',
+        'label' => t('Summary text value'),
+      );
+      $definitions['summary_processed'] = array(
+        'type' => 'string',
+        'label' => t('Processed summary text'),
+        'description' => t('The summary text value with the text format applied.'),
+        'html' => TRUE,
+        'computed' => TRUE,
+        'class' => '\Drupal\text\PropertyProcessedText',
+        'source' => 'summary',
+      );
+    }
+    return $definitions;
+  }
+}
+
diff --git a/core/modules/field/modules/text/text.module b/core/modules/field/modules/text/text.module
index dcf4d1e..fbf8945 100644
--- a/core/modules/field/modules/text/text.module
+++ b/core/modules/field/modules/text/text.module
@@ -38,6 +38,7 @@ function text_field_info() {
       'instance_settings' => array('text_processing' => 0),
       'default_widget' => 'text_textfield',
       'default_formatter' => 'text_default',
+      'property class' => '\Drupal\text\PropertyTextItem',
     ),
     'text_long' => array(
       'label' => t('Long text'),
@@ -45,6 +46,7 @@ function text_field_info() {
       'instance_settings' => array('text_processing' => 0),
       'default_widget' => 'text_textarea',
       'default_formatter' => 'text_default',
+      'property class' => '\Drupal\text\PropertyTextItem',
     ),
     'text_with_summary' => array(
       'label' => t('Long text and summary'),
@@ -52,6 +54,7 @@ function text_field_info() {
       'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
       'default_widget' => 'text_textarea_with_summary',
       'default_formatter' => 'text_default',
+      'property class' => '\Drupal\text\PropertyTextWithSummaryItem',
     ),
   );
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 4cf5780..3da276e 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1868,6 +1868,29 @@ function system_stream_wrappers() {
 }
 
 /**
+ * Implements hook_data_type_info().
+ */
+function system_data_type_info() {
+  return array(
+    'string' => array(
+      'label' => t('String'),
+      'class' => '\Drupal\Core\Data\Type\String',
+      'primitive' => TRUE,
+    ),
+    'integer' => array(
+      'label' => t('Integer'),
+      'class' => '\Drupal\Core\Data\Type\Integer',
+      'primitive' => TRUE,
+    ),
+    'language' => array(
+      'label' => t('Language'),
+      'description' => t('A language object.'),
+      'class' => '\Drupal\Core\Data\Type\Language',
+    ),
+  );
+}
+
+/**
  * Retrieve a blocked IP address from the database.
  *
  * @param $iid integer
