blob: 0ef770dbb6fb1b433dd48f70fc3777e17cda7594 [file] [log] [blame]
// Copyright (c) 2016, 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.
library observable.src.records;
import 'package:collection/collection.dart';
import 'package:quiver/core.dart';
import 'internal.dart';
part 'records/list_change_record.dart';
part 'records/map_change_record.dart';
part 'records/property_change_record.dart';
/// Result of a change to an observed object.
class ChangeRecord {
/// Signifies a change occurred, but without details of the specific change.
///
/// May be used to produce lower-GC-pressure records where more verbose change
/// records will not be used directly.
static const ANY = ChangeRecords<ChangeRecord>.any();
/// Signifies no changes occurred.
static const NONE = ChangeRecords<ChangeRecord>.none();
const ChangeRecord();
}
/// Represents a list of change records.
///
/// The motivation for implementing the list interface is to fix a typing
/// issue with ChangeRecord.ANY while maintaining backwards compatibility with
/// existing code.
class ChangeRecords<RecordType extends ChangeRecord>
extends DelegatingList<RecordType> {
// This is a covariant unfortunately because generics cannot be used in a
// const constructor. Should be sound however since the equality check does
// not do any mutations.
static const _listEquals = ListEquality<ChangeRecord>();
final bool _isAny;
final List<RecordType> _delegate;
/// Represents any change where the list of changes is irrelevant.
const ChangeRecords.any() : this._(const [], true);
/// Represents a null change where nothing happened.
const ChangeRecords.none() : this._(const [], false);
/// Wraps around a list of records.
///
/// Note: this wraps around a shallow copy of [list]. If [list] is modified,
/// then it is modified within this change record as well. This is provide a
/// const constructor for [ChangeRecords].
const ChangeRecords.wrap(List<RecordType> list) : this._(list, false);
/// Creates a change record list from a deep copy of [it].
ChangeRecords.fromIterable(Iterable<RecordType> it)
: this._(List.unmodifiable(it), false);
const ChangeRecords._(this._delegate, this._isAny) : super(_delegate);
@override
int get hashCode => hash2(_delegate, _isAny);
/// Equal if this and [other] have the same generic type and either both are
/// any records or both are not any records and have the same list of entries.
///
/// E.g.
/// ChangeRecords<CR1>.any() == ChangeRecords<CR1>.any()
/// ChangeRecords<CR1>.any() != ChangeRecords<CR2>.any()
///
/// List of records checked with deep comparison.
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ChangeRecords &&
runtimeType == other.runtimeType &&
((_isAny && other._isAny) ||
(!_isAny &&
!other._isAny &&
_listEquals.equals(_delegate, other._delegate)));
@override
String toString() =>
_isAny ? 'ChangeRecords.any' : 'ChangeRecords($_delegate)';
}