001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023–2025 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;
020import java.lang.constant.MethodHandleDesc;
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025import java.util.Objects;
026import java.util.Optional;
027
028import javax.lang.model.type.TypeMirror;
029
030import org.microbean.constant.Constables;
031
032import org.microbean.qualifier.NamedAttributeMap;
033
034import org.microbean.scope.ScopeMember;
035
036import static java.lang.constant.ConstantDescs.BSM_INVOKE;
037import static java.lang.constant.ConstantDescs.CD_boolean;
038import static java.lang.constant.ConstantDescs.CD_int;
039import static java.lang.constant.ConstantDescs.CD_List;
040import static java.lang.constant.ConstantDescs.FALSE;
041import static java.lang.constant.ConstantDescs.TRUE;
042
043import static org.microbean.bean.BeanTypes.legalBeanType;
044
045import static org.microbean.bean.ConstantDescs.CD_Id;
046
047import static org.microbean.qualifier.ConstantDescs.CD_NamedAttributeMap;
048
049/**
050 * An identifier for a {@link Bean}.
051 *
052 * @param types a {@link List} of {@link TypeMirror}s
053 *
054 * @param attributes a {@link List} of {@link NamedAttributeMap}s
055 *
056 * @param governingScopeId a {@link NamedAttributeMap} identifying the <dfn>scope</dfn> to which this {@link Id}
057 * logically belongs
058 *
059 * @param alternate whether this {@link Id} is to be considered an <dfn>alternate</dfn>
060 *
061 * @param rank the {@linkplain Ranked rank} of this {@link Id}
062 *
063 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
064 *
065 * @see Ranked
066 *
067 * @see ScopeMember
068 */
069public final record Id(List<TypeMirror> types,
070                       List<NamedAttributeMap<?>> attributes,
071                       NamedAttributeMap<?> governingScopeId,
072                       boolean alternate,
073                       int rank)
074  implements Constable, Ranked, ScopeMember {
075
076
077  /*
078   * Constructors.
079   */
080
081
082  /**
083   * Creates a new {@link Id} that is not an alternate and that has a {@linkplain Ranked#DEFAULT_RANK default rank}.
084   *
085   * @param types a {@link List} of {@link TypeMirror}s; must not be {@code null}; must not be {@linkplain
086   * List#isEmpty() empty}
087   *
088   * @param attributes a {@link List} of {@link NamedAttributeMap}s; must not be {@code null}
089   *
090   * @param governingScopeId a {@link NamedAttributeMap} identifying the <dfn>scope</dfn> to which this {@link Id}
091   * logically belongs; must not be {@code null}
092   *
093   * @exception NullPointerException if {@code types}, {@code attributes}, or {@code governingScopeId} is {@code null}
094   */
095  public Id(final List<TypeMirror> types,
096            final List<NamedAttributeMap<?>> attributes,
097            final NamedAttributeMap<?> governingScopeId) {
098    this(types, attributes, governingScopeId, false, Ranked.DEFAULT_RANK);
099  }
100
101  /**
102   * Creates a new {@link Id} that is not an alternate.
103   *
104   * @param types a {@link List} of {@link TypeMirror}s; must not be {@code null}; must not be {@linkplain
105   * List#isEmpty() empty}
106   *
107   * @param attributes a {@link List} of {@link NamedAttributeMap}s; must not be {@code null}
108   *
109   * @param governingScopeId a {@link NamedAttributeMap} identifying the <dfn>scope</dfn> to which this {@link Id}
110   * logically belongs; must not be {@code null}
111   *
112   * @param rank the {@linkplain Ranked rank} of this {@link Id}
113   *
114   * @exception NullPointerException if {@code types}, {@code attributes}, or {@code governingScopeId} is {@code null}
115   */
116  public Id(final List<TypeMirror> types,
117            final List<NamedAttributeMap<?>> attributes,
118            final NamedAttributeMap<?> governingScopeId,
119            final int rank) {
120    this(types, attributes, governingScopeId, false, rank);
121  }
122
123  /**
124   * Creates a new {@link Id}.
125   *
126   * @param types a {@link List} of {@link TypeMirror}s; must not be {@code null}; must not be {@linkplain
127   * List#isEmpty() empty}
128   *
129   * @param attributes a {@link List} of {@link NamedAttributeMap}s; must not be {@code null}
130   *
131   * @param governingScopeId a {@link NamedAttributeMap} identifying the <dfn>scope</dfn> to which this {@link Id}
132   * logically belongs; must not be {@code null}
133   *
134   * @param alternate whether this {@link Id} is to be considered an <dfn>alternate</dfn>
135   *
136   * @param rank the {@linkplain Ranked rank} of this {@link Id}
137   *
138   * @exception NullPointerException if {@code types}, {@code attributes}, or {@code governingScopeId} is {@code null}
139   */
140  public Id {
141    // The code below jumps through some hoops to avoid copying the types list if possible.
142    final int size = types.size();
143    if (size == 0) {
144      throw new IllegalArgumentException("types.isEmpty()");
145    }
146    int i = 0;
147    for (; i < size; i++) {
148      if (!legalBeanType(types.get(i))) {
149        break;
150      }
151    }
152    if (i == size) {
153      types = List.copyOf(types);
154    } else {
155      final ArrayList<TypeMirror> newTypes = new ArrayList<>(size);
156      for (int j = 0; j < i; j++) {
157        newTypes.add(types.get(j)); // the type is known to be legal
158      }
159      ++i; // skip past the illegal type i was pointing to
160      for (; i < size; i++) {
161        final TypeMirror t = types.get(i);
162        if (legalBeanType(t)) {
163          newTypes.add(t);
164        }
165      }
166      if (newTypes.isEmpty()) {
167        throw new IllegalArgumentException("types contains no legal bean types: " + types);
168      }
169      newTypes.trimToSize();
170      types = Collections.unmodifiableList(newTypes);
171    }
172    attributes = List.copyOf(attributes);
173    Objects.requireNonNull(governingScopeId, "governingScopeId");
174  }
175
176
177  /*
178   * Instance methods.
179   */
180
181  
182  @Override // Constable
183  public final Optional<DynamicConstantDesc<Id>> describeConstable() {
184    return Constables.describeConstable(this.attributes())
185      .flatMap(attributesDesc -> this.governingScopeId().describeConstable()
186               .flatMap(governingScopeIdDesc -> Constables.describeConstable(this.types())
187                        .map(typesDesc -> DynamicConstantDesc.of(BSM_INVOKE,
188                                                                 MethodHandleDesc.ofConstructor(CD_Id,
189                                                                                                CD_List,
190                                                                                                CD_List,
191                                                                                                CD_NamedAttributeMap,
192                                                                                                CD_boolean,
193                                                                                                CD_int),
194                                                                 typesDesc,
195                                                                 attributesDesc,
196                                                                 governingScopeIdDesc,
197                                                                 this.alternate() ? TRUE : FALSE,
198                                                                 this.rank()))));
199  }
200
201}