| // 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'); |
| } |
| } |
| } |