001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.inject;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.concurrent.*;
023import java.util.function.*;
024
025/**
026 * Stack-aware {@link BeanStore} overlay.
027 *
028 * <p>
029 * A {@code StackOverlay} delegates to a LIFO stack of {@link BeanStore} frames.  Each lookup walks the stack
030 * from top to bottom and returns the first match.  When the stack is empty the lookup returns
031 * {@link Optional#empty()} so that an outer chain (typically a {@link BasicBeanStore}'s {@code overridingParent}
032 * slot) can fall through to its remaining tiers.
033 *
034 * <p>
035 * This class is designed to live in the {@code overridingParent} slot of a {@link BasicBeanStore}.  Pushing a
036 * frame inserts a new tier-1 layer; popping it removes that layer.  Stack order is enforced LIFO &mdash; popping
037 * out of order or popping an empty stack throws {@link IllegalStateException}.
038 *
039 * <p>
040 * The implementation uses a {@link ConcurrentLinkedDeque} so that reads from concurrent HTTP request threads
041 * see a consistent view while pushes and pops are in flight.  Pushes/pops are expected to be infrequent (test
042 * setup/teardown); reads are the hot path.
043 *
044 * <p>
045 * <b>Composition, not inheritance.</b>  {@code StackOverlay} {@code implements} {@link BeanStore} rather than
046 * extending {@link BasicBeanStore} so that it does <i>not</i> inherit the entries / defaults / parent storage
047 * that a {@link BasicBeanStore} provides.  A {@code StackOverlay} is purely a stack of pluggable bean stores.
048 *
049 * <h5 class='section'>Example:</h5>
050 * <p class='bjava'>
051 *    <jc>// Create an overlay-capable bean store.</jc>
052 *    StackOverlay <jv>overlay</jv> = <jk>new</jk> StackOverlay();
053 *    BasicBeanStore <jv>store</jv> = <jk>new</jk> BasicBeanStore(<jk>null</jk>, <jv>overlay</jv>);
054 *
055 *    <jc>// Push a per-test override frame.</jc>
056 *    <jv>overlay</jv>.push(<jk>new</jk> BasicBeanStore().addBean(MyService.<jk>class</jk>, <jv>mock</jv>));
057 *    <jv>store</jv>.getBean(MyService.<jk>class</jk>);  <jc>// returns the mock</jc>
058 *
059 *    <jc>// Pop it at end of test.</jc>
060 *    <jv>overlay</jv>.pop();
061 * </p>
062 *
063 * <h5 class='section'>See Also:</h5><ul>
064 *    <li class='jc'>{@link BasicBeanStore} - The bean store this overlay sits in front of.
065 * </ul>
066 *
067 * @since 10.0.0
068 */
069public final class StackOverlay implements BeanStore {
070
071   private final Deque<BeanStore> frames = new ConcurrentLinkedDeque<>();
072
073   /**
074    * Constructor.
075    */
076   public StackOverlay() { /* no-op */ }
077
078   /**
079    * Pushes a new overlay frame onto the top of the stack.
080    *
081    * <p>
082    * The frame becomes the highest-priority lookup target for subsequent {@link #getBean(Class)} /
083    * {@link #getBeanSupplier(Class)} calls until it is popped.
084    *
085    * @param overlay The bean store to push.  Must not be <jk>null</jk> or an {@link IllegalArgumentException} is thrown.
086    * @return This object.
087    */
088   public StackOverlay push(BeanStore overlay) {
089      reqnn("overlay", overlay);
090      frames.push(overlay);
091      return this;
092   }
093
094   /**
095    * Pops the top frame off the stack.
096    *
097    * @return The popped frame.
098    * @throws IllegalStateException If the stack is empty.
099    */
100   public BeanStore pop() {
101      var f = frames.pollFirst();
102      if (f == null)
103         throw new IllegalStateException("Cannot pop from an empty StackOverlay.");
104      return f;
105   }
106
107   /**
108    * Returns the current depth of the stack.
109    *
110    * @return The number of frames currently on the stack.
111    */
112   public int depth() {
113      return frames.size();
114   }
115
116   /**
117    * Returns the top frame on the stack without removing it.
118    *
119    * @return The top frame, or <jk>null</jk> if the stack is empty.
120    */
121   public BeanStore peek() {
122      return frames.peekFirst();
123   }
124
125   @Override /* BeanStore */
126   public <T> Optional<T> getBean(Class<T> beanType) {
127      return getBean(beanType, null);
128   }
129
130   @Override /* BeanStore */
131   public <T> Optional<T> getBean(Class<T> beanType, String name) {
132      for (var f : frames) {
133         var b = f.getBean(beanType, name);
134         if (b.isPresent())
135            return b;
136      }
137      return oe();
138   }
139
140   @Override /* BeanStore */
141   public <T> Map<String,T> getBeansOfType(Class<T> beanType) {
142      // Walk the stack bottom-to-top so that higher (later-pushed) frames overwrite lower frames
143      // for entries with the same name.  Iterating an iterator gives top-to-bottom; we reverse
144      // that here by snapshotting and walking the reversed iterator.
145      Map<String,T> result = m();
146      var snapshot = new ArrayList<>(frames);
147      for (var i = snapshot.size() - 1; i >= 0; i--)
148         snapshot.get(i).getBeansOfType(beanType).forEach(result::put);
149      return result;
150   }
151
152   @Override /* BeanStore */
153   public boolean hasBean(Class<?> beanType) {
154      return hasBean(beanType, null);
155   }
156
157   @Override /* BeanStore */
158   public boolean hasBean(Class<?> beanType, String name) {
159      for (var f : frames)
160         if (f.hasBean(beanType, name))
161            return true;
162      return false;
163   }
164
165   @Override /* BeanStore */
166   public <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType) {
167      return getBeanSupplier(beanType, null);
168   }
169
170   @Override /* BeanStore */
171   public <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType, String name) {
172      for (var f : frames) {
173         var s = f.getBeanSupplier(beanType, name);
174         if (s.isPresent())
175            return s;
176      }
177      return oe();
178   }
179
180   @Override /* BeanStore */
181   public <T> Optional<Class<? extends T>> getBeanType(Class<T> beanType) {
182      for (var f : frames) {
183         var t = f.getBeanType(beanType);
184         if (t.isPresent())
185            return t;
186      }
187      return oe();
188   }
189
190   @Override /* Overridden from Object */
191   public String toString() {
192      // Intentionally avoid recursing into frame.toString() — frames may back-reference the outer
193      // BasicBeanStore that holds this overlay in its overridingParent slot, which would cause
194      // stack overflows via BasicBeanStore.properties().  A flat representation (depth + identity
195      // of each frame) is sufficient for diagnostics.
196      var sb = new StringBuilder("StackOverlay{depth=").append(frames.size()).append(", frames=[");
197      var first = true;
198      for (var f : frames) {
199         if (!first)
200            sb.append(", ");
201         first = false;
202         sb.append(cns(f))
203            .append('@')
204            .append(Integer.toHexString(System.identityHashCode(f)));
205      }
206      return sb.append("]}").toString();
207   }
208}