001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2022–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.scope;
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;
021import java.lang.constant.MethodTypeDesc;
022
023import java.util.List;
024import java.util.Optional;
025
026import org.microbean.attributes.Attributes;
027
028import static java.lang.constant.ConstantDescs.BSM_INVOKE;
029import static java.lang.constant.ConstantDescs.CD_boolean;
030import static java.lang.constant.ConstantDescs.FALSE;
031import static java.lang.constant.ConstantDescs.TRUE;
032
033import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
034
035import static org.microbean.assign.Qualifiers.primordialQualifier;
036import static org.microbean.assign.Qualifiers.qualifier;
037
038/**
039 * A representation of the defining characteristics of a <dfn>scope</dfn>, a logical entity that manages the lifecycle
040 * of objects.
041 *
042 * <p>A {@link Scope} is either a <dfn>{@linkplain #normal() normal} scope</dfn> or a
043 * <dfn>pseudo-scope</dfn>. Implementations of normal scopes permit their objects to have circular dependencies, whereas
044 * pseudo-scopes do not. Pragmatically, objects managed by normal scopes are usually instances of a particular kind of
045 * <dfn>proxy</dfn>, namely a <dfn>client proxy</dfn>. Objects managed by pseudo-scopes may or may not be proxied, but
046 * are not client proxies.</p>
047 *
048 * <p>A {@link Scope} is notionally <dfn>governed</dfn> by another scope which manages its lifecycle. A {@link Scope}
049 * that reports that it is governed by itself is known as the <dfn>primordial scope</dfn>. All scopes are ultimately
050 * governed by the primordial scope.</p>
051 *
052 * <p>In any program using {@link Scope}s, behavior is undefined if there exist two {@link Scope}s with equal
053 * {@linkplain #id() identifiers} but any other differing attributes. Any collection of {@link Scope}s, in other words,
054 * must be a {@link java.util.Set set}.</p>
055 *
056 * <p>Behavior is also undefined if any two scopes declare each other, by any means, as their respective governing
057 * scopes.</p>
058 *
059 * @param id a non-{@code null} {@link Attributes} identifying this {@link Scope}
060 *
061 * @param normal whether this {@link Scope} is <dfn>normal</dfn>
062 *
063 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
064 */
065public record Scope(Attributes id, boolean normal) implements Constable {
066
067
068  /*
069   * Static fields.
070   */
071
072
073  /**
074   * An {@link Attributes} identifying the <dfn>scope</dfn> designator.
075   */
076  public static final Attributes SCOPE = Attributes.of("Scope");
077
078
079  /**
080   * An {@link Attributes} identifying the (well-known) <dfn>singleton pseudo-scope</dfn>.
081   *
082   * <p>The {@link Attributes} constituting the singleton pseudo-scope identifier is {@linkplain Attributes#attributes()
083   * attributed} with {@linkplain #scope() the scope designator}, {@linkplain
084   * org.microbean.assign.Qualifiers#qualifier() the qualifier designator}, and {@linkplain
085   * org.microbean.assign.Qualifiers#primordialQualifier() the primordial qualifier}, indicating that the scope it
086   * identifies governs itself.</p>
087   *
088   * @see #SINGLETON
089   */
090  public static final Attributes SINGLETON_ID = Attributes.of("Singleton", qualifier(), scope(), primordialQualifier());
091
092  /**
093   * The (well-known and primordial) <dfn>singleton pseudo-scope</dfn>.
094   *
095   * @see #SINGLETON_ID
096   */
097  public static final Scope SINGLETON = Scope.of(SINGLETON_ID, false);
098
099
100  /**
101   * An {@link Attributes} identifying the (well-known and {@linkplain #normal() normal}) <dfn>application scope</dfn>.
102   *
103   * @see #APPLICATION
104   */
105  public static final Attributes APPLICATION_ID = Attributes.of("Application", qualifier(), scope(), SINGLETON_ID);
106
107  /**
108   * The (well-known and {@linkplain #normal() normal}) <dfn>application scope</dfn>.
109   *
110   * @see #APPLICATION_ID
111   */
112  public static final Scope APPLICATION = Scope.of(APPLICATION_ID, true);
113
114
115  /**
116   * An{@link Attributes} identifying the (well-known) <dfn>none pseudo-scope</dfn>.
117   *
118   * @see #NONE
119   */
120  public static final Attributes NONE_ID = Attributes.of("None", qualifier(), scope(), SINGLETON_ID);
121
122  /**
123   * The (well-known) <dfn>none pseudo-scope</dfn>.
124   *
125   * @see #NONE_ID
126   */
127  public static final Scope NONE = Scope.of(NONE_ID, false);
128
129
130  /*
131   * Constructors.
132   */
133
134
135  /**
136   * Creates a new {@link Scope}.
137   *
138   * @param id a non-{@code null} {@link Attributes} identifying this {@link Scope}; must be a {@linkplain #scope()
139   * scope}
140   *
141   * @param normal whether this {@link Scope} is <dfn>normal</dfn>
142   *
143   * @exception NullPointerException if any argument is {@code null}
144   *
145   * @exception IllegalArgumentException if any argument is not a {@linkplain #scope() scope}
146   *
147   * @see #of(Attributes, boolean)
148   */
149  public Scope {
150    final Attributes qualifier = qualifier();
151    final Attributes scope = scope();
152    final List<Attributes> attributes = id.attributes();
153    if (!attributes.contains(scope) || !attributes.contains(qualifier)) {
154      throw new IllegalArgumentException("id: " + id);
155    }
156    Attributes governingScopeId = null;
157    for (final Attributes a : attributes) {
158      if (primordialQualifier(a)) {
159        governingScopeId = id;
160        break;
161      } else {
162        final List<Attributes> l = a.attributes();
163        if (l.contains(scope) && l.contains(qualifier)) {
164          governingScopeId = a;
165          break;
166        }
167      }
168    }
169    if (governingScopeId == null) {
170      throw new IllegalArgumentException("id: " + id);
171    }
172  }
173
174
175  /*
176   * Instance methods.
177   */
178
179
180  /**
181   * Returns a non-{@code null} {@link Optional} housing a {@link ConstantDesc} describing this {@link Scope}, or an {@linkplain
182   * Optional#empty() empty} {@link Optional} if this {@link Scope} could not be so described.
183   *
184   * @return a non-{@code null} {@link Optional}
185   */
186  @Override // Constable
187  public final Optional<? extends ConstantDesc> describeConstable() {
188    final ClassDesc CD_Scope = ClassDesc.of(Scope.class.getName());
189    final ClassDesc CD_Attributes = ClassDesc.of(Attributes.class.getName());
190    return this.id().describeConstable()
191      .map(idDesc -> DynamicConstantDesc.of(BSM_INVOKE,
192                                            MethodHandleDesc.ofMethod(STATIC,
193                                                                      CD_Scope,
194                                                                      "of",
195                                                                      MethodTypeDesc.of(CD_Scope,
196                                                                                        CD_Attributes,
197                                                                                        CD_boolean)),
198                                            idDesc,
199                                            this.normal() ? TRUE : FALSE));
200  }
201
202
203  /*
204   * Static methods.
205   */
206
207
208  /**
209   * Returns a {@link Scope} suitable for the supplied arguments.
210   *
211   * @param id a non-{@code null} {@link Attributes} identifying this {@link Scope}
212   *
213   * @param normal whether this {@link Scope} is <dfn>normal</dfn>
214   *
215   * @return a non-{@code null} {@link Scope}
216   *
217   * @exception NullPointerException if any argument is {@code null}
218   *
219   */
220  // This method is referenced by the describeConstable() method.
221  public static final Scope of(final Attributes id, final boolean normal) {
222    return new Scope(id, normal);
223  }
224
225  /**
226   * Returns the {@linkplain #SCOPE scope designator}.
227   *
228   * @return a non-{@code null} {@link Attributes}
229   */
230  public static final Attributes scope() {
231    return SCOPE;
232  }
233
234}