001/* 002 * JDrupes Builder 003 * Copyright (C) 2026 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.core; 020 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.List; 024import java.util.concurrent.Callable; 025import java.util.concurrent.CopyOnWriteArrayList; 026import java.util.concurrent.ExecutorService; 027import java.util.concurrent.Future; 028import java.util.function.Supplier; 029 030/// Supports preserving scoped values in another context. 031/// 032/// Scoped values are bound in a thread context. However, their binding 033/// is no longer available when an action is executed in another thread 034/// or as a callback. 035/// 036public final class ScopedValueTracker { 037 038 private static List<ScopedValue<?>> registry = new CopyOnWriteArrayList<>(); 039 040 /// A snapshot of the the values of the registered scoped value instances. 041 /// 042 public static final class Snapshot { 043 private final List<ScopedValue<?>> scoped = new ArrayList<>(registry); 044 private final List<Object> values = new ArrayList<>(scoped.size()); 045 046 private Snapshot() { 047 for (var scopedVar : scoped) { 048 values.add(scopedVar.isBound() ? scopedVar.get() : null); 049 } 050 } 051 052 @SuppressWarnings("unchecked") 053 private ScopedValue.Carrier carriers() { 054 var scopedIterator = scoped.iterator(); 055 var valuesIterator = values.iterator(); 056 ScopedValue.Carrier carriers = null; 057 if (scopedIterator.hasNext()) { 058 carriers = ScopedValue.where( 059 (ScopedValue<Object>) scopedIterator.next(), 060 valuesIterator.next()); 061 } 062 while (scopedIterator.hasNext()) { 063 carriers = carriers.where( 064 (ScopedValue<Object>) scopedIterator.next(), 065 valuesIterator.next()); 066 } 067 return carriers; 068 } 069 070 /// Get the value from the given supplier after restoring the 071 /// values of the registered scoped value instances. 072 /// 073 /// @param <T> the generic type 074 /// @param action the action 075 /// @return the result 076 /// 077 public <T> T withGet(Supplier<T> action) { 078 ScopedValue.Carrier carriers = carriers(); 079 if (carriers == null) { 080 return action.get(); 081 } 082 return carriers.call(action::get); 083 } 084 085 /// Execute the given task after restoring the values of the 086 /// registered scoped value instances. 087 /// 088 /// @param <T> the generic type 089 /// @param task the action 090 /// @return the result 091 /// 092 @SuppressWarnings("PMD.SignatureDeclareThrowsException") 093 public <T> T withCall(Callable<T> task) throws Exception { 094 ScopedValue.Carrier carriers = carriers(); 095 if (carriers == null) { 096 return task.call(); 097 } 098 return carriers.call(task::call); 099 } 100 } 101 102 private ScopedValueTracker() { 103 // Make javadoc happy 104 } 105 106 /// Adds the value to the registry. 107 /// 108 /// @param values the values 109 /// 110 public static void add(ScopedValue<?>... values) { 111 Arrays.asList(values).forEach(registry::add); 112 } 113 114 /// Creates a new snapshot. 115 /// 116 /// @return the snapshot 117 /// 118 public static Snapshot snapshot() { 119 return new Snapshot(); 120 } 121 122 /// Executes the task with the registered scoped values inherited 123 /// from the current thread. 124 /// 125 /// @param <T> the generic type 126 /// @param executor the executor 127 /// @param task the task 128 /// @return the future 129 /// 130 public static <T> Future<T> submitTo(ExecutorService executor, 131 Callable<T> task) { 132 // Capture values 133 Snapshot snapshot = snapshot(); 134 return executor.submit(() -> { 135 return snapshot.withCall(task); 136 }); 137 } 138}