2323import  java .util .List ;
2424import  java .util .Map ;
2525import  java .util .Set ;
26- import  java .util .concurrent .ConcurrentHashMap ;
26+ import  java .util .concurrent .* ;
2727import  java .util .concurrent .atomic .AtomicInteger ;
28+ import  java .util .function .Function ;
2829
2930import  org .apache .commons .logging .Log ;
3031import  org .apache .commons .logging .LogFactory ;
31- import  org .jspecify .annotations .Nullable ;
3232
33+ import  org .jspecify .annotations .Nullable ;
3334import  org .springframework .context .ApplicationContext ;
3435import  org .springframework .context .ConfigurableApplicationContext ;
3536import  org .springframework .core .style .ToStringCreator ;
@@ -57,11 +58,12 @@ public class DefaultContextCache implements ContextCache {
5758
5859	private  static  final  Log  statsLogger  = LogFactory .getLog (CONTEXT_CACHE_LOGGING_CATEGORY );
5960
61+ 	ExecutorService  executorService  = Executors .newFixedThreadPool (Runtime .getRuntime ().availableProcessors ()); //TODO: Make this parametric 
6062
6163	/** 
6264	 * Map of context keys to Spring {@code ApplicationContext} instances. 
6365	 */ 
64- 	private  final  Map <MergedContextConfiguration , ApplicationContext > contextMap  =
66+ 	private  final  Map <MergedContextConfiguration , Future < ApplicationContext > > contextMap  =
6567			Collections .synchronizedMap (new  LruCache (32 , 0.75f ));
6668
6769	/** 
@@ -120,25 +122,69 @@ public boolean contains(MergedContextConfiguration key) {
120122		return  this .contextMap .containsKey (key );
121123	}
122124
123- 	@ Override 
125+ 	@ Override //TODO: This is not used anymore in spring but make sense to keep it for retro compatibility, right? 
124126	public  @ Nullable  ApplicationContext  get (MergedContextConfiguration  key ) {
125127		Assert .notNull (key , "Key must not be null" );
126- 		ApplicationContext  context  = this .contextMap .get (key );
128+ 
129+ 		try  {
130+ 			Future <ApplicationContext > context  = this .contextMap .get (key );
131+ 
127132		if  (context  == null ) {
128133			this .missCount .incrementAndGet ();
134+ 
135+ 				return  null ;
129136		}
130137		else  {
138+ 			this .hitCount .incrementAndGet ();
139+ 
140+ 				return  context .get ();
141+ 		}
142+ 		} catch  (InterruptedException  e ) {
143+ 			throw  new  RuntimeException (e );//FIXME: fix the message 
144+ 		} catch  (ExecutionException  e ) {
145+ 			throw  new  RuntimeException (e );//FIXME: fix the message 
146+ 		}
147+ 	}
148+ 
149+ 
150+ 	@ Override 
151+ 	public  Future <ApplicationContext > computeIfAbsent (MergedContextConfiguration  key , Function <MergedContextConfiguration , ApplicationContext > mappingFunction ) {
152+ 		Assert .notNull (key , "Key must not be null" );
153+ 
154+ 		if (contextMap .containsKey (key )) {
131155			this .hitCount .incrementAndGet ();
132156		}
133- 		return  context ;
157+ 
158+ 		return  contextMap .computeIfAbsent (key , (k ) ->
159+ 				{
160+ 					this .missCount .incrementAndGet ();
161+ 					return  CompletableFuture .supplyAsync (() -> mappingFunction .apply (k ), executorService )
162+ 							.thenApply (
163+ 									(contextLoaded ) -> {
164+ 										MergedContextConfiguration  child  = key ;
165+ 										MergedContextConfiguration  parent  = child .getParent ();
166+ 										while  (parent  != null ) {
167+ 											Set <MergedContextConfiguration > list  = this .hierarchyMap .computeIfAbsent (parent , k2  -> new  HashSet <>());
168+ 											list .add (child );
169+ 											child  = parent ;
170+ 											parent  = child .getParent ();
171+ 										}
172+ 
173+ 										return  contextLoaded ;
174+ 									}
175+ 							);
176+ 
177+ 				}
178+ 		);
134179	}
135180
181+ 	//TODO: This is not used anymore in spring but make sense to keep it for retro compatibility, right? 
136182	@ Override 
137183	public  void  put (MergedContextConfiguration  key , ApplicationContext  context ) {
138184		Assert .notNull (key , "Key must not be null" );
139185		Assert .notNull (context , "ApplicationContext must not be null" );
140186
141- 		this .contextMap .put (key , context );
187+ 		this .contextMap .put (key , CompletableFuture . completedFuture ( context ) );
142188		MergedContextConfiguration  child  = key ;
143189		MergedContextConfiguration  parent  = child .getParent ();
144190		while  (parent  != null ) {
@@ -198,10 +244,19 @@ private void remove(List<MergedContextConfiguration> removedContexts, MergedCont
198244
199245		// Physically remove and close leaf nodes first (i.e., on the way back up the 
200246		// stack as opposed to prior to the recursive call). 
201- 		ApplicationContext  context  = this .contextMap .remove (key );
247+ 		Future <ApplicationContext > contextLoader  = this .contextMap .remove (key );
248+ 
249+ 		try  {
250+ 			ApplicationContext  context  = contextLoader .get ();
202251		if  (context  instanceof  ConfigurableApplicationContext  cac ) {
203252			cac .close ();
204253		}
254+ 		} catch  (InterruptedException  e ) {
255+ 			throw  new  RuntimeException (e ); //FIXME: fix the message 
256+ 		} catch  (ExecutionException  e ) {
257+ 			throw  new  RuntimeException (e ); //FIXME: fix the message 
258+ 		}
259+ 
205260		removedContexts .add (key );
206261	}
207262
@@ -303,7 +358,7 @@ public String toString() {
303358	 * @since 4.3 
304359	 */ 
305360	@ SuppressWarnings ("serial" )
306- 	private  class  LruCache  extends  LinkedHashMap <MergedContextConfiguration , ApplicationContext > {
361+ 	private  class  LruCache  extends  LinkedHashMap <MergedContextConfiguration , Future < ApplicationContext > > {
307362
308363		/** 
309364		 * Create a new {@code LruCache} with the supplied initial capacity 
@@ -316,7 +371,7 @@ private class LruCache extends LinkedHashMap<MergedContextConfiguration, Applica
316371		}
317372
318373		@ Override 
319- 		protected  boolean  removeEldestEntry (Map .Entry <MergedContextConfiguration , ApplicationContext > eldest ) {
374+ 		protected  boolean  removeEldestEntry (Map .Entry <MergedContextConfiguration , Future < ApplicationContext > > eldest ) {
320375			if  (this .size () > DefaultContextCache .this .getMaxSize ()) {
321376				// Do NOT delete "DefaultContextCache.this."; otherwise, we accidentally 
322377				// invoke java.util.Map.remove(Object, Object). 
0 commit comments