Skip to content

Commit

Permalink
Wire up some of the implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nirinchev committed Dec 4, 2023
1 parent 274952e commit 3545760
Show file tree
Hide file tree
Showing 11 changed files with 1,736 additions and 69 deletions.
3 changes: 2 additions & 1 deletion generator/lib/src/dart_type_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ extension DartTypeEx on DartType {
}
if (self.isDartCoreMap) {
final mappedMap = provider.mapType(self.typeArguments.first, mapped);
return PseudoType('Realm${mappedMap.getDisplayString(withNullability: true)}', nullabilitySuffix: mappedMap.nullabilitySuffix);
return PseudoType('RealmMap<${mappedMap.typeArguments.last.getDisplayString(withNullability: true)}>',
nullabilitySuffix: mappedMap.nullabilitySuffix);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions generator/lib/src/realm_field_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class RealmFieldInfo {
if (isMixed && !type.isRealmCollection) return ' = const RealmValue.nullValue()';
if (hasDefaultValue) return ' = ${fieldElement.initializerExpression}';
if (type.isDartCoreSet) return ' = const {}';
if (type.isDartCoreMap) return ' = const {}';
return ''; // no initializer
}

Expand Down
8 changes: 8 additions & 0 deletions lib/src/collections.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ class CollectionChanges {
const CollectionChanges(this.deletions, this.insertions, this.modifications, this.modificationsAfter, this.moves, this.isCleared);
}

class MapChanges {
final List<String> deletions;
final List<String> insertions;
final List<String> modifications;

const MapChanges(this.deletions, this.insertions, this.modifications);
}

/// Describes the changes in a Realm collection since the last time the notification callback was invoked.
class RealmCollectionChanges implements Finalizable {
final RealmCollectionChangesHandle _handle;
Expand Down
4 changes: 1 addition & 3 deletions lib/src/list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ class ManagedRealmList<T extends Object?> with RealmEntity, ListMixin<T> impleme
late RealmObjectMetadata targetMetadata;
late Type type;
if (T == RealmValue) {
final tuple = realm.metadata.getByClassKey(realmCore.getClassKey(value));
type = tuple.item1;
targetMetadata = tuple.item2;
(type, targetMetadata) = realm.metadata.getByClassKey(realmCore.getClassKey(value));
} else {
targetMetadata = _metadata!;
type = T;
Expand Down
285 changes: 285 additions & 0 deletions lib/src/map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:async';
import 'dart:collection';

import 'package:collection/collection.dart' as collection;

import 'dart:ffi';

import 'collections.dart';
import 'native/realm_core.dart';
import 'realm_object.dart';
import 'realm_class.dart';
import 'results.dart';

abstract class RealmMap<T extends Object?> with RealmEntity implements MapBase<String, T>, Finalizable {
/// Gets a value indicating whether this collection is still valid to use.
///
/// Indicates whether the [Realm] instance hasn't been closed,
/// if it represents a to-many relationship
/// and it's parent object hasn't been deleted.
bool get isValid;

factory RealmMap._(RealmMapHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmMap._(handle, realm, metadata);

/// Creates an unmanaged RealmList from [items]
factory RealmMap(Map<String, T> items) => UnmanagedRealmMap(items);

/// Creates a frozen snapshot of this `RealmList`.
RealmMap<T> freeze();

/// Allows listening for changes when the contents of this collection changes.
Stream<RealmMapChanges<T>> get changes;
}

class UnmanagedRealmMap<T extends Object?> extends collection.DelegatingMap<String, T> with RealmEntity implements RealmMap<T> {
UnmanagedRealmMap([Map<String, T>? items]) : super(Map<String, T>.from(items ?? <String, T>{}));

@override
bool get isValid => true;

@override
RealmMap<T> freeze() => throw RealmStateError("Unmanaged maps can't be frozen");

@override
Stream<RealmMapChanges<T>> get changes => throw RealmStateError("Unmanaged lists don't support changes");
}

class ManagedRealmMap<T extends Object?> with RealmEntity, MapMixin<String, T> implements RealmMap<T> {
final RealmMapHandle _handle;

late final RealmObjectMetadata? _metadata;

ManagedRealmMap._(this._handle, Realm realm, this._metadata) {
setRealm(realm);
}

@override
int get length => realmCore.mapGetSize(handle);

@override
T? remove(Object? key) {
if (key is! String) {
return null;
}

final value = this[key];
if (realmCore.mapRemoveKey(handle, key)) {
return value;
}

return null;
}

@override
T? operator [](Object? key) {
if (key is! String) {
return null;
}

try {
var value = realmCore.mapGetElement(this, key);
if (value is RealmObjectHandle) {
late RealmObjectMetadata targetMetadata;
late Type type;
if (T == RealmValue) {
(type, targetMetadata) = realm.metadata.getByClassKey(realmCore.getClassKey(value));
} else {
targetMetadata = _metadata!;
type = T;
}
value = realm.createObject(type, value, targetMetadata);
}

if (T == RealmValue) {
value = RealmValue.from(value);
}

return value as T?;
} on Exception catch (e) {
throw RealmException("Error getting value at key $key. Error: $e");
}
}

@override
void operator []=(String key, Object? value) => RealmMapInternal.setValue(handle, realm, key, value);

/// Removes all objects from this list; the length of the list becomes zero.
/// The objects are not deleted from the realm, but are no longer referenced from this list.
@override
void clear() => realmCore.mapClear(handle);

@override
bool get isValid => realmCore.mapIsValid(this);

@override
RealmMap<T> freeze() {
if (isFrozen) {
return this;
}

final frozenRealm = realm.freeze();
return frozenRealm.resolveMap(this)!;
}

@override
Stream<RealmMapChanges<T>> get changes {
if (isFrozen) {
throw RealmStateError('Map is frozen and cannot emit changes');
}
final controller = MapNotificationsController<T>(asManaged());
return controller.createStream();
}

@override
Iterable<String> get keys => RealmResultsInternal.create<String>(realmCore.mapGetKeys(this), realm, null);

@override
Iterable<T> get values => RealmResultsInternal.create<T>(realmCore.mapGetValues(this), realm, metadata);

@override
bool containsKey(Object? key) => key is String && realmCore.mapContainsKey(this, key);

@override
bool containsValue(Object? value) {
if (value is! T?) {
return false;
}

if (value is RealmObjectBase && !value.isManaged) {
return false;
}

if (value is RealmValue && value.value is RealmObjectBase && !(value.value as RealmObjectBase).isManaged) {
return false;
}

return realmCore.mapContainsValue(this, value);
}
}

/// Describes the changes in a Realm map collection since the last time the notification callback was invoked.
class RealmMapChanges<T extends Object?> {
/// The collection being monitored for changes.
final RealmMap<T> map;

final RealmMapChangesHandle _handle;
MapChanges? _values;

RealmMapChanges._(this._handle, this.map);

MapChanges get _changes => _values ??= realmCore.getMapChanges(_handle);

/// The keys of the map which have been removed.
List<String> get deleted => _changes.deletions;

/// The keys of the map which were added.
List<String> get inserted => _changes.insertions;

/// The keys of the map, whose corresponding values were modified in this version.
List<String> get modified => _changes.modifications;
}

/// @nodoc
extension RealmMapInternal<T extends Object?> on RealmMap<T> {
@pragma('vm:never-inline')
void keepAlive() {
final self = this;
if (self is ManagedRealmMap<T>) {
realm.keepAlive();
self._handle.keepAlive();
}
}

ManagedRealmMap<T> asManaged() => this is ManagedRealmMap<T> ? this as ManagedRealmMap<T> : throw RealmStateError('$this is not managed');

RealmMapHandle get handle {
final result = asManaged()._handle;
if (result.released) {
throw RealmClosedError('Cannot access a map that belongs to a closed Realm');
}

return result;
}

RealmObjectMetadata? get metadata => asManaged()._metadata;

static RealmMap<T> create<T extends Object?>(RealmMapHandle handle, Realm realm, RealmObjectMetadata? metadata) =>
ManagedRealmMap<T>._(handle, realm, metadata);

static void setValue(RealmMapHandle handle, Realm realm, String key, Object? value, {bool update = false}) {
try {
if (value is EmbeddedObject) {
if (value.isManaged) {
throw RealmError("Can't add to map an embedded object that is already managed");
}

final objHandle = realmCore.mapInsertEmbeddedObject(realm, handle, key);
realm.manageEmbedded(objHandle, value);
return;
}

if (value is RealmValue) {
value = value.value;
}

if (value is RealmObject && !value.isManaged) {
realm.add<RealmObject>(value, update: update);
}

realmCore.mapInsertValue(handle, key, value);
} on Exception catch (e) {
throw RealmException("Error setting value at key $key. Error: $e");
}
}
}

/// @nodoc
class MapNotificationsController<T extends Object?> extends NotificationsController {
final ManagedRealmMap<T> map;
late final StreamController<RealmMapChanges<T>> streamController;

MapNotificationsController(this.map);

@override
RealmNotificationTokenHandle subscribe() {
return realmCore.subscribeMapNotifications(map, this);
}

Stream<RealmMapChanges<T>> createStream() {
streamController = StreamController<RealmMapChanges<T>>(onListen: start, onCancel: stop);
return streamController.stream;
}

@override
void onChanges(HandleBase changesHandle) {
if (changesHandle is! RealmMapChangesHandle) {
throw RealmError("Invalid changes handle. RealmMapChangesHandle expected");
}

final changes = RealmMapChanges._(changesHandle, map);
streamController.add(changes);
}

@override
void onError(RealmError error) {
streamController.addError(error);
}
}
Loading

0 comments on commit 3545760

Please sign in to comment.