001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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.ArrayDeque;
017import java.util.Collection;
018import java.util.List;
019import java.util.Map;
020import java.util.Objects;
021import java.util.Queue;
022
023import org.microbean.bean.AttributedType;
024import org.microbean.bean.AutoCloseableRegistry;
025import org.microbean.bean.Bean;
026import org.microbean.bean.BeanQualifiersMatcher;
027import org.microbean.bean.BeanReduction;
028import org.microbean.bean.BeanTypeMatcher;
029import org.microbean.bean.Creation;
030import org.microbean.bean.DefaultAutoCloseableRegistry;
031import org.microbean.bean.IdMatcher;
032import org.microbean.bean.InterceptorBindingsMatcher;
033import org.microbean.bean.RankedReducer;
034import org.microbean.bean.Reducer;
035import org.microbean.bean.Reducible;
036import org.microbean.bean.Request;
037import org.microbean.bean.Selectable;
038
039import org.microbean.construct.Domain;
040
041import static org.microbean.bean.Beans.cachingSelectableOf;
042
043/**
044 * A basic implementation of the {@link Request} interface.
045 *
046 * @param <I> the type of contextual instance initiating the request
047 *
048 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
049 */
050public class DefaultRequest<I>
051  implements Request<I>,
052             AutoCloseableRegistry,
053             Reducible<AttributedType, Bean<?>>,
054             Selectable<AttributedType, Bean<?>> {
055
056
057  /*
058   * Static fields.
059   */
060
061
062  private static final ThreadLocal<Queue<Request<?>>> REQUESTS = ThreadLocal.withInitial(ArrayDeque::new);
063
064
065  /*
066   * Instance fields.
067   */
068
069
070  private final AutoCloseableRegistry acr;
071
072  private final BeanReduction<I> beanReduction;
073
074  private final Creation<I> c;
075
076  private final ClientProxier cp;
077
078  private final Instances instances;
079
080  private final Reducible<AttributedType, Bean<?>> reducible;
081
082  private final Selectable<AttributedType, Bean<?>> selectable;
083
084
085  /*
086   * Constructors.
087   */
088
089
090  /**
091   * Creates a new {@link DefaultRequest}.
092   *
093   * @param selectable a {@link Selectable} that selects {@link Bean}s given an {@link AttributedType}; must not be {@code null}
094   *
095   * @param reducible a {@link Reducible} that reduces collections of {@link Bean}s; must not be {@code null}
096   *
097   * @param instances an {@link Instances} implementation; must not be {@code null}
098   *
099   * @param cp a {@link ClientProxier}; must not be {@code null}
100   *
101   * @exception NullPointerException if any argument is {@code null}
102   */
103  public DefaultRequest(final Selectable<AttributedType, Bean<?>> selectable,
104                        final Reducible<AttributedType, Bean<?>> reducible, // normally Reducible.of(selectable, RankedReducer.of())
105                        final Instances instances,
106                        final ClientProxier cp) { // not nullable
107    this(selectable,
108         reducible,
109         instances,
110         null,
111         null,
112         cp,
113         null);
114  }
115
116  /**
117   * Creates a new {@link DefaultRequest}.
118   *
119   * @param selectable a {@link Selectable} that selects {@link Bean}s given an {@link AttributedType}; must not be {@code null}
120   *
121   * @param reducible a {@link Reducible} that reduces collections of {@link Bean}s; must not be {@code null}
122   *
123   * @param instances an {@link Instances} implementation; must not be {@code null}
124   *
125   * @param acr an {@link AutoCloseableRegistry}; may be {@code null} in which case a default implementation will be
126   * used instead
127   *
128   * @param c a {@link Creation} implementation; 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 selectable}, {@code reducible}, {@code instances} or {@code cp} is {@code
134   * null}
135   */
136  public DefaultRequest(final Selectable<AttributedType, Bean<?>> selectable,
137                        final Reducible<AttributedType, Bean<?>> reducible, // normally Reducible.of(selectable, RankedReducer.of())
138                        final Instances instances,
139                        final AutoCloseableRegistry acr, // nullable
140                        final Creation<I> c, // nullable
141                        final ClientProxier cp) { // not nullable
142    this(selectable,
143         reducible,
144         instances,
145         acr,
146         c,
147         cp,
148         null);
149  }
150
151  private DefaultRequest(final Selectable<AttributedType, Bean<?>> selectable,
152                         final Reducible<AttributedType, Bean<?>> reducible, // normally Reducible.of(selectable, RankedReducer.of())
153                         final Instances instances,
154                         final AutoCloseableRegistry acr, // nullable
155                         final Creation<I> c, // nullable
156                         final ClientProxier cp, // not nullable
157                         final BeanReduction<I> beanReduction) { // nullable, but only for the first one
158    super();
159    this.selectable = Objects.requireNonNull(selectable, "selectable");
160    this.reducible = Objects.requireNonNull(reducible, "reducible");
161    this.instances = Objects.requireNonNull(instances, "instances");
162    this.cp = Objects.requireNonNull(cp, "cp");
163    this.beanReduction = beanReduction;
164    this.acr = acr == null ? new DefaultAutoCloseableRegistry() : acr;
165    this.c = c == null ? i -> {} : c;
166  }
167
168
169  /*
170   * Instance methods.
171   */
172
173
174  @Override // Request<I>
175  public final BeanReduction<I> beanReduction() {
176    return this.beanReduction; // will be null for the first/primordial Request and no other
177  }
178
179  @Override // Request<I>
180  @SuppressWarnings("unchecked")
181  public <J> DefaultRequest<J> child(final BeanReduction<J> beanReduction) {
182    if (beanReduction.equals(this.beanReduction)) {
183      return (DefaultRequest<J>)this;
184    }
185    final DefaultRequest<J> child =
186      new DefaultRequest<J>(this.selectable,
187                            this.reducible,
188                            this.instances,
189                            this.newChild(), // NOTE
190                            (Creation<J>)this.c,
191                            this.cp,
192                            beanReduction);
193    if (!this.register(child)) {
194      throw new IllegalStateException();
195    }
196    return child;
197  }
198
199  @Override // AutoCloseableRegistry, Instances (AutoCloseable), Request (AutoCloseable)
200  public void close() {
201    this.acr.close();
202    this.instances.close();
203  }
204
205  @Override // AutoCloseableRegistry
206  public boolean closed() {
207    return this.acr.closed();
208  }
209
210  @Override // Creation<I>
211  public final void created(final I i) {
212    this.c.created(i);
213  }
214
215  @Override // AutoCloseableRegistry
216  public final AutoCloseableRegistry newChild() {
217    return this.acr.newChild();
218  }
219
220  @Override // Reducible<AttributedType, Bean<?>> // mostly for convenience
221  public final Bean<?> reduce(final AttributedType attributedType) {
222    return this.reducible.reduce(attributedType);
223  }
224
225  @Override // Request<I>
226  @SuppressWarnings("unchecked")
227  public final <R> R reference(final AttributedType attributedType) {
228    return this.reference(new BeanReduction<>(attributedType, this.reduce(attributedType).cast()), (Request<R>)this);
229  }
230
231  @Override // ReferenceSelector
232  public final <R> R reference(final AttributedType attributedType, final Creation<R> c) {
233    return this.reference(new BeanReduction<>(attributedType, this.reduce(attributedType).cast()), this.toRequest(c));
234  }
235
236  @Override // ReferenceSelector
237  public final <R> R reference(final AttributedType attributedType, final Bean<R> bean, final Creation<R> c) {
238    return this.reference(new BeanReduction<>(attributedType, bean == null ? this.reduce(attributedType).cast() : bean.cast()), this.toRequest(c));
239  }
240
241  // Must not call any other reference() method
242  final <R> R reference(final BeanReduction<R> beanReduction, Request<R> r) {
243    final Request<R> request = (r == null ? this : r).child(beanReduction); // child(beanReduction) is critical
244    final Queue<Request<?>> requests = REQUESTS.get();
245    requests.offer(request); // push
246    try {
247      return
248        this.instances.proxiable(request) ?
249        this.cp.clientProxy(request, this.instances.supplier(request)) :
250        this.instances.supplier(request).get();
251    } finally {
252      requests.poll(); // pop
253    }
254  }
255
256  @Override // AutoCloseableRegistry
257  public boolean register(final AutoCloseable closeable) {
258    return this.acr.register(closeable);
259  }
260
261  @Override // Selectable<AttributedType, Bean<?>> // mostly for convenience
262  public final List<Bean<?>> select(final AttributedType attributedType) {
263    return this.selectable.select(attributedType);
264  }
265
266  @SuppressWarnings("unchecked")
267  private final <R> Request<R> toRequest(final Creation<R> c) {
268    // Note: deliberately no child/newChild semantics at all
269    return switch (c) {
270    case null -> null;
271    case Request<R> r -> r;
272    default -> new DefaultRequest<>(this.selectable,
273                                    this.reducible,
274                                    this.instances,
275                                    this.acr,
276                                    c,
277                                    this.cp,
278                                    (BeanReduction<R>)this.beanReduction);
279    };
280  }
281
282}