001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 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.scopelet;
015
016import java.util.ArrayDeque;
017import java.util.Collection;
018import java.util.List;
019import java.util.Map;
020import java.util.Queue;
021
022import org.microbean.bean.Qualifiers;
023
024import org.microbean.attributes.Attributes;
025import org.microbean.attributes.BooleanValue;
026import org.microbean.attributes.Value;
027
028import static java.util.Objects.requireNonNull;
029
030/**
031 * A utility class for working with <dfn>scopes</dfn> and their identifiers.
032 *
033 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
034 */
035public class Scopes { // deliberately not final
036
037  private static final Map<String, Value<?>> NORMAL = Map.of("normal", BooleanValue.of(true));
038
039  private static final Map<String, Value<?>> PSEUDO = Map.of("normal", BooleanValue.of(false));
040
041  private static final Attributes SCOPE = Attributes.of("Scope");
042
043  private final Attributes NONE;
044
045  private final Attributes SINGLETON;
046
047  private final Qualifiers qualifiers;
048
049  /**
050   * Creates a new {@link Scopes}.
051   *
052   * @param qualifiers a {@link Qualifiers}; must not be {@code null}
053   *
054   * @exception NullPointerException if {@code qualifiers} is {@code null}
055   */
056  public Scopes(final Qualifiers qualifiers) {
057    super();
058    this.qualifiers = requireNonNull(qualifiers, "qualifiers");
059    this.SINGLETON =
060      Attributes.of("Singleton",
061                    PSEUDO,
062                    Map.of(),
063                    Map.of("Singleton",
064                           List.of(qualifiers.qualifier(),
065                                   SCOPE,
066                                   qualifiers.primordialQualifier())));
067    this.NONE =
068      Attributes.of("None",
069                    PSEUDO,
070                    Map.of(),
071                    Map.of("None",
072                           List.of(qualifiers.qualifier(),
073                                   SCOPE,
074                                   SINGLETON)));
075  }
076
077  /**
078   * Returns the first {@link Attributes} that is present in the supplied {@link Collection} of {@link Attributes}, and
079   * their {@linkplain Attributes#attributes() meta-attributes}, for which an invocation of the {@link
080   * #scope(Attributes)} method returns {@code true}, or {@code null}, if no such {@link Attributes} exists.
081   *
082   * <p>The search is conducted in a breadth-first manner.</p>
083   *
084   * @param c a {@link Collection} of {@link Attributes}; must not be {@code null}
085   *
086   * @return the first {@link Attributes} that is present in the supplied {@link Collection} of {@link Attributes}, and
087   * their {@linkplain Attributes#attributes() meta-attributes}, for which an invocation of the {@link
088   * #scope(Attributes)} method returns {@code true}, or {@code null}, if no such {@link Attributes} exists
089   *
090   * @exception NullPointerException if {@code c} is {@code null}
091   */
092  public Attributes findScope(final Collection<? extends Attributes> c) {
093    if (c.isEmpty()) {
094      return null;
095    }
096    // Breadth first on purpose. Scope Attributes closer to the Attributes they attribute win over Scope Attributes
097    // further away.
098    final Queue<Attributes> q = new ArrayDeque<>(c);
099    while (!q.isEmpty()) {
100      final Attributes a = q.poll();
101      if (this.scope(a)) {
102        return a;
103      }
104      q.addAll(a.attributes());
105    }
106    return null;
107  }
108
109  /**
110   * Returns a non-{@code null}, determinate {@link Attributes} representing the identifier for the <dfn>none</dfn>
111   * scope.
112   *
113   * @return a non-{@code null}, determinate {@link Attributes} representing the identifier for the <dfn>none</dfn>
114   * scope
115   */
116  public Attributes none() {
117    return NONE;
118  }
119
120  /**
121   * Returns a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
122   * be used with a {@linkplain #scope() scope} to designate it as a <dfn>normal scope</dfn>.
123   *
124   * @return a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
125   * be used with a {@linkplain #scope() scope} to designate it as a <dfn>normal scope</dfn>
126   */
127  public Map<String, Value<?>> normal() {
128    return NORMAL;
129  }
130
131  /**
132   * Returns {@code true} if the supplied {@link Attributes} has elements that might be used to indicate that it is a
133   * <dfn>normal scope</dfn>.
134   *
135   * @param a an {@link Attributes}; must not be {@code null}
136   *
137   * @return {@code true} if the supplied {@link Attributes} has elements that might be used to indicate that it is a
138   * <dfn>normal scope</dfn>
139   *
140   * @exception NullPointerException if {@code a} is {@code null}
141   *
142   * @see #scope(Attributes)
143   *
144   * @see #normal()
145   */
146  public boolean normal(final Attributes a) {
147    final BooleanValue v = a.value(this.normal().keySet().iterator().next());
148    return v != null && v.value();
149  }
150
151  /**
152   * Returns a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
153   * be used with a {@linkplain #scope() scope} to designate it as a <dfn>pseudo scope</dfn>.
154   *
155   * @return a non-{@code null}, determinate, immutable {@link Map} of {@linkplain Map#size() size} {@code 1} that can
156   * be used with a {@linkplain #scope() scope} to designate it as a <dfn>pseudo scope</dfn>
157   */
158  public Map<String, Value<?>> pseudo() {
159    return PSEUDO;
160  }
161
162  /**
163   * Returns the non-{@code null}, determinate {@link Attributes} that can be used to designate other {@link Attributes}
164   * as a <dfn>scope</dfn>.
165   *
166   * @return the non-{@code null}, determinate {@link Attributes} that can be used to designate other {@link Attributes}
167   * as a <dfn>scope</dfn>
168   */
169  public Attributes scope() {
170    return SCOPE;
171  }
172
173  /**
174   * Returns {@code true} if and only if the supplied {@link Attributes} is a <dfn>scope identifier</dfn>.
175   *
176   * @param a an {@link Attributes}; must not be {@code null}
177   *
178   * @return {@code true} if and only if the supplied {@link Attributes} is a <dfn>scope identifier</dfn>
179   *
180   * @exception NullPointerException if {@code a} is {@code null}
181   */
182  public boolean scope(final Attributes a) {
183    boolean scopeFound = false;
184    boolean qualifierFound = false;
185    for (final Attributes ma : a.attributes()) {
186      if (scopeFound) {
187        if (!qualifierFound && ma.equals(this.qualifiers.qualifier())) {
188          qualifierFound = true;
189          break;
190        }
191      } else if (qualifierFound) {
192        if (ma.equals(this.scope())) {
193          scopeFound = true;
194          break;
195        }
196      } else if (ma.equals(this.scope())) {
197        // a is annotated with @Scope
198        scopeFound = true;
199      } else if (ma.equals(this.qualifiers.qualifier())) {
200        // a is annotated with @Qualifier
201        qualifierFound = true;
202      }
203    }
204    return scopeFound && qualifierFound;
205  }
206
207  /**
208   * Returns a non-{@code null}, determinate {@link Attributes} representing the identifier for the <dfn>singleton</dfn>
209   * pseudo-scope.
210   *
211   * @return a non-{@code null}, determinate {@link Attributes} representing the identifier for the <dfn>singleton</dfn>
212   * pseudo-scope
213   */
214  public Attributes singleton() {
215    return SINGLETON;
216  }
217
218}