blob: 384e9f5fd9a7dbf7a889faa68d698c01ca280aad [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of '../../db.dart';
/// Represents a unique identifier for a [Model] stored in a datastore.
///
/// The [Key] can be incomplete if it's id is `null`. In this case the id will
/// be automatically allocated and set at commit time.
class Key<T> {
// Either KeyImpl or PartitionImpl
final Object _parent;
final Type? type;
final T? id;
Key(Key parent, this.type, this.id) : _parent = parent {
if (type == null) {
throw ArgumentError('The type argument must not be null.');
}
if (id != null && id is! String && id is! int) {
throw ArgumentError('The id argument must be an integer or a String.');
}
}
Key.emptyKey(Partition partition)
: _parent = partition,
type = null,
id = null;
/// Parent of this [Key].
Key? get parent {
if (_parent is Key) {
return _parent as Key;
}
return null;
}
/// The partition of this [Key].
Partition get partition {
var obj = _parent;
while (obj is! Partition) {
obj = (obj as Key)._parent;
}
return obj;
}
Key<U> append<U>(Type modelType, {U? id}) {
return Key<U>(this, modelType, id);
}
bool get isEmpty => _parent is Partition;
@override
bool operator ==(Object other) {
return other is Key &&
_parent == other._parent &&
type == other.type &&
id == other.id;
}
@override
int get hashCode => _parent.hashCode ^ type.hashCode ^ id.hashCode;
/// Converts `Key<dynamic>` to `Key<U>`.
Key<U> cast<U>() => Key<U>(parent!, type, id as U?);
}
/// Represents a datastore partition.
///
/// A datastore is partitioned into namespaces. The default namespace is
/// `null`.
class Partition {
final String? namespace;
Partition(this.namespace) {
if (namespace == '') {
throw ArgumentError('The namespace must not be an empty string');
}
}
/// Returns an empty [Key].
///
/// Entities where the parent [Key] is empty will create their own entity
/// group.
Key get emptyKey => Key.emptyKey(this);
@override
bool operator ==(Object other) {
return other is Partition && namespace == other.namespace;
}
@override
int get hashCode => namespace.hashCode;
}
/// Superclass for all model classes.
///
/// Every model class has a [id] of type [T] which must be `int` or `String`,
/// and a [parentKey]. The [key] getter is returning the key for the model
/// object.
abstract class Model<T> {
T? id;
Key? parentKey;
Key<T> get key => parentKey!.append(runtimeType, id: id);
}
/// Superclass for all expanded model classes.
///
/// The [ExpandoModel] class adds support for having dynamic properties. You can
/// set arbitrary fields on these models. The expanded values must be values
/// accepted by the [RawDatastore] implementation.
abstract class ExpandoModel<T> extends Model<T> {
final Map<String, Object?> additionalProperties = {};
@override
Object? noSuchMethod(Invocation invocation) {
var name = mirrors.MirrorSystem.getName(invocation.memberName);
if (name.endsWith('=')) name = name.substring(0, name.length - 1);
if (invocation.isGetter) {
return additionalProperties[name];
} else if (invocation.isSetter) {
var value = invocation.positionalArguments[0];
additionalProperties[name] = value;
return value;
} else {
throw ArgumentError('Unsupported noSuchMethod call on ExpandoModel');
}
}
}