// Copyright (c) 2012, 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 dart.core;

/// An [Expando] allows adding new properties to objects.
///
/// Does not work on numbers, strings, booleans, `null`, `dart:ffi` pointers,
/// `dart:ffi` structs, or `dart:ffi` unions.
///
/// An `Expando` does not hold on to the added property value after an object
/// becomes inaccessible.
///
/// Since you can always create a new number that is identical to an existing
/// number, it means that an expando property on a number could never be
/// released. To avoid this, expando properties cannot be added to numbers.
/// The same argument applies to strings, booleans and `null`, which also have
/// literals that evaluate to identical values when they occur more than once.
///
/// There is no restriction on other classes, even for compile time constant
/// objects. Be careful if adding expando properties to compile time constants,
/// since they will stay alive forever.
class Expando<T extends Object> {
  /// The name of the this [Expando] as passed to the constructor.
  ///
  /// If no name was passed to the constructor, the value is the `null` value.
  final String? name;

  /// Creates a new [Expando]. The optional name is only used for
  /// debugging purposes and creating two different [Expando]s with the
  /// same name yields two [Expando]s that work on different properties
  /// of the objects they are used on.
  external Expando([String? name]);

  /// Expando toString method override.
  String toString() => "Expando:$name";

  /// Gets the value of this [Expando]'s property on the given object.
  ///
  /// If the object hasn't been expanded, the result is the `null` value.
  ///
  /// The object must not be a number, a string, a boolean, `null`, a
  /// `dart:ffi` pointer, a `dart:ffi` struct, or a `dart:ffi` union.
  external T? operator [](Object object);

  /// Sets this [Expando]'s property value on the given object to [value].
  ///
  /// Properties can effectively be removed again
  /// by setting their value to `null`.
  ///
  /// The object must not be a number, a string, a boolean, `null`, a
  /// `dart:ffi` pointer, a `dart:ffi` struct, or a `dart:ffi` union.
  external void operator []=(Object object, T? value);
}

/// A weak reference to a Dart object.
///
/// A _weak_ reference to the [target] object which may be cleared
/// (set to reference `null` instead) at any time
/// when there is no other way for the program to access the target object.
///
/// _Being the target of a weak reference does not keep an object
/// from being garbage collected._
///
/// There are no guarantees that a weak reference will ever be cleared
/// even if all references to its target are weak references.
///
/// Not all objects are supported as targets for weak references.
/// The [WeakReference] constructor will reject any object that is not
/// supported as an [Expando] key.
@Since("2.17")
abstract class WeakReference<T extends Object> {
  /// Creates a [WeakReference] pointing to the given [target].
  ///
  /// The [target] must be an object supported as an [Expando] key,
  /// which means [target] cannot be a number, a string, a boolean,
  /// the `null` value, or certain other types of special objects.
  external factory WeakReference(T target);

  /// The current object weakly referenced by [this], if any.
  ///
  /// The value is either the object supplied in the constructor,
  /// or `null` if the weak reference has been cleared.
  T? get target;
}

/// A finalizer which can be attached to Dart objects.
///
/// A finalizer can create attachments between
/// the finalizer and any number of Dart values,
/// by calling [attach] with the value, along with a
/// _finalization token_ and an optional _attach key_,
/// which are part of the attachment.
///
/// When a Dart value becomes inaccessible to the program,
/// any finalizer that currently has an attachment to
/// the value *may* have its callback function called
/// with the attachment's finalization token.
///
/// Example:
/// ```dart template:none
/// // Keep the finalizer itself reachable, otherwise might not do anything.
/// final Finalizer<DBConnection> _finalizer = Finalizer((connection) {
///   connection.close();
/// });
///
/// /// Access the database.
/// Database connect() {
///   // Wraps the connection in a nicer user-facing API,
///   // *and* closes connection if the user forgets to.
///   var connection = _connectToDatabase();
///   var wrapper = Database._fromConnection(connection, _finalizer);
///   // Get finalizer callback when `wrapper` is no longer reachable.
///   _finalizer.attach(wrapper, connection, detach: wrapper);
///   return wrapper;
/// }
///
/// class Database {
///   final DBConnection _connection;
///   final Finalizer<Connection> _finalizer;
///   Database._fromConnection(this._connection, this._finalizer);
///
///   // Some useful methods.
///
///   void close() {
///     // User requested close.
///     _connection.close();
///     // Detach from finalizer, no longer needed.
///     _finalizer.detach(this);
///   }
/// }
/// ```
/// This example has an example of an external resource that needs clean-up.
/// The finalizer is used to clean up an external connection when the
/// user of the API no longer has access to that connection.
/// The example uses the same object as attached object and detach key,
/// which is a useful approach when each attached object can be detached
/// individually. Being a detachment key doesn't keep an object alive.
///
/// No promises are made that the callback will ever be called.
/// The only thing that is guaranteed is that if a finalizer's callback
/// is called with a specific finalization token as argument,
/// then at least one value with an attachment to to the finalizer
/// that has that finalization token,
/// is no longer accessible to the program.
///
/// If the finalzier *itself* becomes unreachable,
/// it's allowed to be garbage collected
/// and then it won't trigger any further callbacks.
/// Always make sure to keep the finalizer itself reachable while it's needed.
///
/// If multiple finalizers are attached to a single object,
/// or the same finalizer is attached multiple times to an object,
/// and that object becomes inaccessible to the program,
/// then any number (including zero) of those attachments may trigger
/// their associated finalizer's callback.
/// It will not necessarily be all or none of them.
///
/// Finalization callbacks will happen as *events*.
/// They will not happen during execution of other code,
/// and not as a microtask,
/// but as high-level events similar to timer events.
///
/// Finalization callbacks must not throw.
@Since("2.17")
abstract class Finalizer<T> {
  /// Creates a finalizer with the given finalization callback.
  ///
  /// The [callback] is bound to the current zone
  /// when the [Finalizer] is created, and will run in that zone when called.
  external factory Finalizer(void Function(T) callback);

  /// Attaches this finalizer to [value].
  ///
  /// When [value] is longer accessible to the program,
  /// while still having an attachement to this finalizer,
  /// the callback of this finalizer *may* be called
  /// with [finalizationToken] as argument.
  /// The callback may be called at most once per active attachment,
  /// ones which have not been detached by calling [Finalizer.detach].
  ///
  /// If a non-`null` [detach] value is provided, that object can be
  /// passed to [Finalizer.detach] to remove the attachment again.
  ///
  /// The [value] and [detach] arguments do not count towards those
  /// objects being accessible to the program.
  /// Both must be objects supported as an [Expando] key.
  /// They may be the *same* object.
  ///
  /// Example:
  /// ```dart template:top
  /// /// Access the data base.
  /// Database connect() {
  ///   // Wraps the connection in a nice user API,
  ///   // *and* closes connection if the user forgets to.
  ///   var connection = _connectToDatabase();
  ///   var wrapper = Database._fromConnection(connection, _finalizer);
  ///   // Get finalizer callback when `wrapper` is no longer reachable.
  ///   _finalizer.attach(wrapper, connection, detach: wrapper);
  ///   return wrapper;
  /// }
  /// ````
  ///
  /// Multiple objects may be attached using the same finalization token,
  /// and the finalizer can be attached multiple times to the same object
  /// with different, or the same, finalization token.
  void attach(Object value, T finalizationToken, {Object? detach});

  /// Detaches this finalizer from values attached with [detach].
  ///
  /// Each attachment between this finalizer and a value,
  /// which was created by calling [attach] with the [detach] object as
  /// `detach` argument, is removed.
  ///
  /// If the finalizer was attached multiple times to the same value
  /// with different detachment keys,
  /// only those attachments which used [detach] are removed.
  ///
  /// After detaching, an attachment won't cause any callbacks to happen
  /// if the object become inaccessible.
  ///
  /// Example:
  /// ```dart template:none
  /// final Finalizer<DBConnection> _finalizer = Finalizer((connection) {
  ///   connection.close();
  /// });
  ///
  /// class Database {
  ///   final DBConnection _connection;
  ///   final Finalizer<Connection> _finalizer;
  ///   Database._fromConnection(this._connection, this._finalizer);
  ///
  ///   // Some useful methods.
  ///
  ///   void close() {
  ///     // User requested close.
  ///     _connection.close();
  ///     // Detach from finalizer, no longer needed.
  ///     // Was attached using this object as `detach` token.
  ///     _finalizer.detach(this);
  ///   }
  /// }
  /// ```
  void detach(Object detach);
}
