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 — 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}