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}