001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025–2026 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.bean;
015
016import java.lang.constant.ClassDesc;
017import java.lang.constant.Constable;
018import java.lang.constant.ConstantDesc;
019import java.lang.constant.DynamicConstantDesc;
020
021import java.util.Optional;
022import java.util.SequencedSet;
023
024import java.util.function.Function;
025
026import javax.lang.model.AnnotatedConstruct;
027
028import javax.lang.model.element.Element;
029
030import org.microbean.assign.Aggregate;
031import org.microbean.assign.Annotated;
032import org.microbean.assign.Assignment;
033
034import static java.lang.constant.ConstantDescs.BSM_INVOKE;
035
036import static java.lang.constant.MethodHandleDesc.ofConstructor;
037
038import static java.util.Objects.requireNonNull;
039
040/**
041 * A ({@link Constable}) pairing of an {@link Id} with a {@link Factory}.
042 *
043 * @param <I> the type of the contextual instances the associated {@link Factory} creates
044 *
045 * @param id the {@link Id}; must not be {@code null}
046 *
047 * @param factory the {@link Factory}; must not be {@code null}
048 *
049 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
050 *
051 * @see Factory
052 *
053 * @see Id
054 */
055public final record Bean<I>(Id id, Factory<I> factory) implements Aggregate, Constable {
056
057  /**
058   * Creates a new {@link Bean}.
059   *
060   * @param id the {@link Id}; must not be {@code null}
061   *
062   * @param factory the {@link Factory}; must not be {@code null}
063   *
064   * @exception NullPointerException if either argument is {@code null}
065   */
066  public Bean {
067    requireNonNull(id, "id");
068    requireNonNull(factory, "factory");
069  }
070
071  // (Convenience.)
072  @Override // Aggregate
073  public final SequencedSet<? extends Assignment<?>> assign(final Function<? super Annotated<? extends AnnotatedConstruct>, ?> r) {
074    return this.factory.assign(r);
075  }
076
077  /**
078   * Returns this {@link Bean}, forcibly cast appropriately.
079   *
080   * @param <X> the type of contextual instances this {@link Bean} creates
081   *
082   * @return this {@link Bean}
083   *
084   * @exception ClassCastException if the cast is inappropriate
085   */
086  @SuppressWarnings("unchecked")
087  public final <X> Bean<X> cast() {
088    return (Bean<X>)this;
089  }
090
091  // (Convenience.)
092  @Override // Aggregate
093  public final SequencedSet<? extends Annotated<? extends Element>> dependencies() {
094    return this.factory.dependencies();
095  }
096
097  @Override // Constable
098  public final Optional<DynamicConstantDesc<Bean<I>>> describeConstable() {
099    return (this.factory instanceof Constable c ? c.describeConstable() : Optional.<ConstantDesc>empty())
100      .flatMap(factoryDesc -> this.id.describeConstable()
101               .map(idDesc -> DynamicConstantDesc.of(BSM_INVOKE,
102                                                     ofConstructor(ClassDesc.of(Bean.class.getName()),
103                                                                   ClassDesc.of(Id.class.getName()),
104                                                                   ClassDesc.of(Factory.class.getName())),
105                                                     idDesc,
106                                                     factoryDesc)));
107  }
108
109  @Override // Record (Object)
110  public final boolean equals(final Object other) {
111    return this == other || switch (other) {
112    case null -> false;
113    case Bean<?> b when this.getClass() == b.getClass() -> this.id.equals(b.id);
114    default -> false;
115    };
116  }
117
118  @Override // Record (Object)
119  public final int hashCode() {
120    return this.id.hashCode();
121  }
122
123}