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}