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}