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.reference;
015
016import java.util.Iterator;
017
018import java.util.function.Supplier;
019
020import org.microbean.assign.AttributedType;
021import org.microbean.assign.Selectable;
022
023import org.microbean.bean.Bean;
024import org.microbean.bean.BeanException;
025import org.microbean.bean.Creation;
026import org.microbean.bean.Destruction;
027import org.microbean.bean.Id;
028import org.microbean.bean.References;
029import org.microbean.bean.ReferencesSelector;
030
031import org.microbean.construct.Domain;
032
033import org.microbean.proxy.Proxy;
034
035import static java.util.Collections.emptyIterator;
036
037import static java.util.Objects.requireNonNull;
038
039/**
040 * A central object representing a request for dependencies that is a {@link Creation} (and therefore also a {@link
041 * Destruction}), a {@link DestructorRegistry}, and a {@link References}.
042 *
043 * <p>Instances of this class are the heart and soul of a dependency injection and acquisition system.</p>
044 *
045 * @param <I> the contextual instance type being instantiated (see for example {@link Creation})
046 *
047 * @param <R> the contextual reference type being sought (see {@link References})
048 *
049 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
050 *
051 * @see Creation
052 *
053 * @see Destruction
054 *
055 * @see DestructorRegistry
056 *
057 * @see References
058 *
059 * @see org.microbean.bean.Factory#create(Creation)
060 *
061 * @see org.microbean.bean.Factory#destroy(Object, Destruction)
062 */
063public final class Request<I, R> implements Creation<I>, Destruction, DestructorRegistry, References<R> {
064
065
066  /*
067   * Instance fields.
068   */
069
070
071  private final Domain domain;
072
073  private final Selectable<? super AttributedType, Bean<?>> beans;
074
075  private final Instances instances;
076
077  private final DestructorTree destructorTree;
078
079  private final ClientProxier cp;
080
081  private final Bean<I> b; // nullable; B and R must then be (effectively) Void
082
083  private final AttributedType rType; // nullable; R must then be Void
084
085
086  /*
087   * Constructors.
088   */
089
090
091  /**
092   * Creates a new {@link Request}.
093   *
094   * @param d a {@link Domain}; must not be {@code null}
095   *
096   * @param s a {@link Selectable} providing access to {@link Bean}s by {@link AttributedType}; must not be {@code
097   * null}; must be safe for concurrent use by multiple threads; often assembled out of methods present in the {@link
098   * org.microbean.bean.Selectables} and {@link org.microbean.bean.Beans} classes, among other such utility classes
099   *
100   * @param instances an {@link Instances} responsible for using a {@link Bean} to acquire an appropriate {@link
101   * Supplier} of contextual instances; must not be {@code null}
102   *
103   * @param cp a {@link ClientProxier}; must not be {@code null}
104   *
105   * @exception NullPointerException if any argument is {@code null}
106   *
107   * @see #Request(Domain, Selectable, Instances, DestructorTree, ClientProxier)
108   */
109  public Request(final Domain d,
110                 final Selectable<? super AttributedType, Bean<?>> s,
111                 final Instances instances,
112                 final ClientProxier cp) {
113    this(d, s, instances, null, cp, null, null);
114  }
115
116  /**
117   * Creates a new {@link Request}.
118   *
119   * @param d a {@link Domain}; must not be {@code null}
120   *
121   * @param s a {@link Selectable} providing access to {@link Bean}s by {@link AttributedType}; must not be {@code
122   * null}; must be safe for concurrent use by multiple threads; often assembled out of methods present in the {@link
123   * org.microbean.bean.Selectables} and {@link org.microbean.bean.Beans} classes, among other such utility classes
124   *
125   * @param instances an {@link Instances} responsible for using a {@link Bean} to acquire an appropriate {@link
126   * Supplier} of contextual instances; must not be {@code null}
127   *
128   * @param destructorTree a {@link DestructorTree}; may be {@code null} in which case a default implementation will be used
129   * instead
130   *
131   * @param cp a {@link ClientProxier}; must not be {@code null}
132   *
133   * @exception NullPointerException if {@code s}, {@code instances}, or {@code cp} is {@code null}
134   */
135  public Request(final Domain d,
136                 final Selectable<? super AttributedType, Bean<?>> s,
137                 final Instances instances,
138                 final DestructorTree destructorTree, // nullable
139                 final ClientProxier cp) {
140    this(d, s, instances, destructorTree, cp, null, null);
141  }
142
143  private Request(final Domain d,
144                  final Selectable<? super AttributedType, Bean<?>> s,
145                  final Instances instances,
146                  final DestructorTree destructorTree, // nullable
147                  final ClientProxier cp,
148                  final Bean<I> b, // nullable
149                  final AttributedType rType) { // the type of the references returned (<R>); nullable
150    this.domain = requireNonNull(d, "d");
151    this.beans = requireNonNull(s, "s");
152    this.instances = requireNonNull(instances, "instances");
153    this.cp = requireNonNull(cp, "cp");
154    this.destructorTree = destructorTree == null ? new DefaultDestructorTree() : destructorTree;
155    this.b = b;
156    this.rType = rType;
157  }
158
159
160  /*
161   * Instance methods.
162   */
163
164
165  @Override // Destruction
166  public final void close() {
167    this.destructorTree.close();
168  }
169
170  @Override // ReferencesSelector
171  public final boolean destroy(final Object r) {
172    final Destructor destructor = this.destructorTree.remove(r instanceof Proxy<?> p ? p.$proxied() : r);
173    if (destructor != null) {
174      destructor.destroy(); // I keep going back and forth on whether this should be under some kind of lock, or whether the Destructor contract covers it
175      return true;
176    }
177    return false;
178  }
179
180  @Override // ReferencesSelector
181  public final Domain domain() {
182    return this.domain;
183  }
184
185  @Override // Creation<I>
186  public final Id id() {
187    return this.b == null ? null : this.b.id();
188  }
189
190  @Override // References<R> (Iterable<R>)
191  public final Iterator<R> iterator() {
192    return new ReferencesIterator(); // inner class; see below
193  }
194
195  @Override // ReferencesSelector
196  public final <R> R reference(final Bean<R> bean) {
197    final Supplier<? extends R> supplier = this.instances.supplier(bean, this.newChild(bean)); // newChild is critical
198    final Id id = bean.id();
199    return this.instances.proxiable(id) ? this.cp.clientProxy(id, supplier) : supplier.get();
200  }
201
202  @Override // ReferencesSelector
203  @SuppressWarnings("unchecked")
204  public final <X> References<X> references(final AttributedType rType) {
205    return this.rType == rType ? (References<X>)this :
206      // This basically returns "this" but with a new rType. But Request is immutable so we make a copy.
207      new Request<>(this.domain,
208                    this.beans,
209                    this.instances,
210                    this.destructorTree, // deliberately NO this.destructorTree.newChild() call
211                    this.cp,
212                    this.b, // nullable; <I> will then be (effectively) Void
213                    rType); // nullable; <X> will then be Void
214  }
215
216  @Override // DestructorTree (DestructorRegistry)
217  public final boolean register(final Object reference, final Destructor destructor) {
218    return this.destructorTree.register(reference, destructor);
219  }
220
221  @Override // References<R>
222  public final int size() {
223    return this.rType == null ? 0 : this.beans.select(this.rType).size();
224  }
225
226  /*
227   * Private instance methods.
228   */
229
230  private final Iterator<Bean<?>> beanIterator() {
231    return this.rType == null ? emptyIterator() : this.beans.select(this.rType).iterator();
232  }
233
234  @SuppressWarnings("unchecked")
235  private final <X> Request<X, ?> newChild(final Bean<X> b) {
236    if (b == null) {
237      if (this.b == null) {
238        return (Request<X, R>)this; // both <X> and <R> are effectively Void
239      }
240    } else if (b.equals(this.b)) {
241      return (Request<X, R>)this;
242    }
243    return
244      new Request<X, Void>(this.domain,
245                           this.beans,
246                           this.instances,
247                           this.destructorTree.newChild(), // critical; !b.equals(this.b)
248                           this.cp,
249                           b, // nullable; if so, <X> better resolve to Void
250                           null); // rType; <R> resolves to Void
251  }
252
253
254  /*
255   * Inner and nested classes.
256   */
257
258
259  // NOT thread-safe.
260  private final class ReferencesIterator implements Iterator<R> {
261
262    private Iterator<Bean<?>> i;
263
264    private R ref;
265
266    private ReferencesIterator() {
267      super();
268      if (rType == null) {
269        this.i = emptyIterator();
270      }
271    }
272
273    @Override // Iterator<R>
274    public final boolean hasNext() {
275      if (this.i == null) {
276        this.i = beanIterator();
277      }
278      return this.i.hasNext();
279    }
280
281    @Override // Iterator<R>
282    @SuppressWarnings("unchecked")
283    public final R next() {
284      if (this.i == null) {
285        this.i = beanIterator();
286      }
287      return this.ref = reference((Bean<R>)this.i.next());
288    }
289
290    @Override // Iterator<R>
291    public final void remove() {
292      final R ref = this.ref;
293      if (ref == null) {
294        throw new IllegalStateException(); // per Iterator#remove() contract
295      }
296      this.ref = null;
297      destroy(ref);
298    }
299
300  }
301
302}