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.bean;
015
016import java.util.Collection;
017import java.util.Collections;
018import java.util.LinkedHashSet;
019import java.util.Objects;
020import java.util.SequencedSet;
021
022import org.microbean.interceptor.InterceptionFunction;
023import org.microbean.interceptor.InterceptorMethod;
024
025import static org.microbean.interceptor.Interceptions.ofConstruction;
026
027/**
028 * A {@link Producer} that applies constructor interception to produce contextual instances.
029 *
030 * @param <I> the type of contextual instance
031 *
032 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
033 *
034 * @see InterceptorMethod
035 */
036// Applies around-construct logic to contextual instance production.
037public final class InterceptingProducer<I> implements Producer<I> {
038
039  private final InterceptionFunction f;
040
041  private final Producer<I> producer;
042
043  /**
044   * Creates a new {@link InterceptingProducer}.
045   *
046   * @param interceptorMethods a {@link Collection} of {@link InterceptorMethod}s; must not be {@code null}
047   *
048   * @param producer a subordinate {@link Producer} to which are delegated the {@link #assign(Request)}, {@link
049   * #dependencies()}, {@link #dispose(Object, Request)} and {@link #produce(SequencedSet)} operations; must not be
050   * {@code null}
051   *
052   * @exception NullPointerException if either argument is {@code null}
053   */
054  @SuppressWarnings("unchecked")
055  public InterceptingProducer(final Collection<? extends InterceptorMethod> interceptorMethods,
056                              final Producer<I> producer) {
057    super();
058    this.producer = producer;
059    final SequencedSet<AttributedElement> dependencies = producer.dependencies();
060    this.f = ofConstruction(interceptorMethods, (ignored, argumentsArray) -> {
061        final SequencedSet<Assignment<?>> assignments = new LinkedHashSet<>();
062        int i = 0;
063        for (final AttributedElement dependency : dependencies) {
064          assignments.add(new Assignment<>(dependency, argumentsArray[i++]));
065        }
066        return this.produce(Collections.unmodifiableSequencedSet(assignments));
067      });
068  }
069
070  /**
071   * Calls the {@link #assign(Request) assign(Request)} method on the {@linkplain #InterceptingProducer(Collection,
072   * Producer) <code>Producer</code> supplied at construction time} with the supplied {@link Request} and returns the
073   * result.
074   *
075   * @param r a {@link Request}; must not be {@code null}
076   *
077   * @return the result of calling the {@link #assign(Request) assign(Request)} method on the {@linkplain
078   * #InterceptingProducer(Collection, Producer) <code>Producer</code> supplied at construction time}; never {@code
079   * null}
080   *
081   * @exception NullPointerException if {@code r} is {@code null}
082   *
083   * @see #InterceptingProducer(Collection, Producer)
084   *
085   * @see Producer#assign(Request)
086   */
087  @Override // Producer<I> (Aggregate)
088  public final SequencedSet<? extends Assignment<?>> assign(final Request<?> r) {
089    return this.producer.assign(r);
090  }
091
092  /**
093   * Calls the {@link #dependencies() dependencies()} method on the {@linkplain #InterceptingProducer(Collection,
094   * Producer) <code>Producer</code> supplied at construction time} and returns the result.
095   *
096   * @return the result of calling the {@link #dependencies() dependencies()} method on the {@linkplain
097   * #InterceptingProducer(Collection, Producer) <code>Producer</code> supplied at construction time}; never {@code
098   * null}
099   *
100   * @see #InterceptingProducer(Collection, Producer)
101   *
102   * @see Aggregate#dependencies()
103   */
104  @Override // Producer<I> (Aggregate)
105  public final SequencedSet<AttributedElement> dependencies() {
106    return this.producer.dependencies();
107  }
108
109  /**
110   * Calls the {@link #dispose(Object, Request) dispose(Object, Request)} method on the {@linkplain
111   * #InterceptingProducer(Collection, Producer) <code>Producer</code> supplied at construction time} with the supplied
112   * {@code i} and the supplied {@link Request}.
113   *
114   * @param i a contextual instance produced by this {@link InterceptingProducer}; may be {@code null}
115   *
116   * @param r a {@link Request}; must not be {@code null}
117   *
118   * @exception NullPointerException if {@code r} is {@code null}
119   *
120   * @see #InterceptingProducer(Collection, Producer)
121   *
122   * @see Producer#assign(Request)
123   */
124  @Override // Producer<I>
125  public final void dispose(final I i, final Request<I> r) {
126    this.producer.dispose(i, r);
127  }
128
129  /**
130   * Produces a potentially uninitialized contextual instance and returns it, using the {@linkplain
131   * #InterceptingProducer(Collection, Producer) <code>Collection</code> of <code>InterceptorMethod</code>s supplied at
132   * construction time} to intercept the production.
133   *
134   * @param r a {@link Request}; must not be {@code null}
135   *
136   * @return a contextual instance, which may be {@code null}
137   *
138   * @exception NullPointerException if {@code r} is {@code null}
139   *
140   * @see #InterceptingProducer(Collection, Producer)
141   *
142   * @see Producer#produce(Request)
143   */
144  @Override // Producer<I>
145  @SuppressWarnings("unchecked")
146  public final I produce(final Request<?> r) {
147    final Collection<? extends AttributedElement> dependencies = this.dependencies();
148    final Object[] array = new Object[dependencies.size()];
149    int i = 0;
150    for (final AttributedElement d : dependencies) {
151      array[i++] = r.reference(d.attributedType());
152    }
153    return (I)this.f.apply(array);
154  }
155
156  /**
157   * Calls the {@link #produce(SequencedSet) produce(SequencedSet)} method on the {@linkplain
158   * #InterceptingProducer(Collection, Producer) <code>Producer</code> supplied at construction time} with the supplied
159   * {@code assignments} and returns the result.
160   *
161   * @param assignments a {@link SequencedSet} of {@link Assignment}s; must not be {@code null}
162   *
163   * @exception NullPointerException if {@code assignments} is {@code null}
164   *
165   * @see #InterceptingProducer(Collection, Producer)
166   *
167   * @see Producer#produce(SequencedSet)
168   */
169  @Override // Producer<I>
170  public final I produce(final SequencedSet<? extends Assignment<?>> assignments) {
171    return this.producer.produce(assignments);
172  }
173
174}