/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.instrumentation.api.util;

import io.opentelemetry.instrumentation.api.internal.RuntimeVirtualFieldSupplier;
import javax.annotation.Nullable;

/**
 * Represents a "virtual" field of type {@code F} that is added to type {@code T} in the runtime.
 *
 * <p>A virtual field has similar semantics to a weak-keys strong-values map: the value will be
 * garbage collected when their owner instance is collected. It is discouraged to use a virtual
 * field for keeping values that might reference their key, as it may cause memory leaks.
 *
 * @param <T> The type that will contain the new virtual field.
 * @param <F> The field type that'll be added to {@code T}.
 */
// we're using an abstract class here so that we can call static find() in pre-jdk8 advice classes
public abstract class VirtualField<T, F> {

  /**
   * Finds a {@link VirtualField} instance for given {@code type} and {@code fieldType}.
   *
   * <p>Conceptually this can be thought of as a map lookup to fetch a second level map given {@code
   * type}.
   *
   * <p>In runtime, when using the javaagent, the <em>calls</em> to this method are rewritten to
   * something more performant while injecting advice into a method.
   *
   * <p>When using this method outside of Advice method, the {@link VirtualField} should be looked
   * up once and stored in a field to avoid repeatedly calling this method.
   *
   * @param type The type that will contain the new virtual field.
   * @param fieldType The field type that will be added to {@code type}.
   */
  public static <U extends T, V extends F, T, F> VirtualField<U, V> find(
      Class<T> type, Class<F> fieldType) {
    return RuntimeVirtualFieldSupplier.get().find(type, fieldType);
  }

  /** Gets the value of this virtual field. */
  @Nullable
  public abstract F get(T object);

  /** Sets the new value of this virtual field. */
  public abstract void set(T object, @Nullable F fieldValue);
}
