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.lang.ScopedValue.CallableOp; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Optional; 027import java.util.concurrent.Callable; 028import java.util.concurrent.CopyOnWriteArrayList; 029import java.util.concurrent.ExecutorService; 030import java.util.concurrent.Future; 031import java.util.function.Function; 032 033/// Supports using scoped values in another context. 034/// 035/// Scoped values are bound in a thread context. However, their binding 036/// is no longer available when an action is executed in another thread 037/// or as a callback. 038/// 039public final class ScopedValueContext { 040 041 private static List<ScopedValue<?>> registry = new CopyOnWriteArrayList<>(); 042 private static final ScopedValue<Boolean> DUMMY = ScopedValue.newInstance(); 043 044 /// A snapshot of the the values of the registered scoped value instances. 045 /// 046 public static final class Snapshot { 047 private final List<ScopedValue<?>> scoped = new LinkedList<>(registry); 048 private final List<Object> values = new ArrayList<>(scoped.size()); 049 050 private Snapshot() { 051 for (var iter = scoped.iterator(); iter.hasNext();) { 052 var scopedVar = iter.next(); 053 if (scopedVar.isBound()) { 054 values.add(scopedVar.get()); 055 } else { 056 iter.remove(); 057 } 058 } 059 } 060 061 @SuppressWarnings("unchecked") 062 private ScopedValue.Carrier carrierList() { 063 var scopedIterator = scoped.iterator(); 064 var valuesIterator = values.iterator(); 065 ScopedValue.Carrier carriers = null; 066 if (scopedIterator.hasNext()) { 067 carriers = ScopedValue.where( 068 (ScopedValue<Object>) scopedIterator.next(), 069 valuesIterator.next()); 070 } 071 while (scopedIterator.hasNext()) { 072 carriers = carriers.where( 073 (ScopedValue<Object>) scopedIterator.next(), 074 valuesIterator.next()); 075 } 076 return carriers; 077 } 078 079 /// Returns the carriers for the values in the snapshot. 080 /// 081 /// @return the scoped value. carrier 082 /// 083 public ScopedValue.Carrier carriers() { 084 return Optional.ofNullable(carrierList()) 085 .orElseGet(() -> ScopedValue.where(DUMMY, Boolean.TRUE)); 086 } 087 088 /// Appends the scoped value and value to the carriers representing 089 /// the snapshot and returns the result. 090 /// 091 /// @param <T> the generic type 092 /// @param key the key 093 /// @param value the value 094 /// @return the scoped value. carrier 095 /// 096 public <T> ScopedValue.Carrier where(ScopedValue<T> key, T value) { 097 return Optional.ofNullable(carrierList()) 098 .map(c -> c.where(key, value)) 099 .orElseGet(() -> ScopedValue.where(key, value)); 100 } 101 102 /// Invokes the appender for adding scoped values to the snapshot 103 /// and returns the result. 104 /// 105 /// @param <T> the generic type 106 /// @param appender the appender 107 /// @return the scoped value. carrier 108 /// 109 public <T> ScopedValue.Carrier where( 110 Function<ScopedValue.Carrier, ScopedValue.Carrier> appender) { 111 return appender.apply(carriers()); 112 } 113 114 /// Short for `carriers().call(op)`. 115 /// 116 /// @param <R> the generic type 117 /// @param <X> the generic type 118 /// @param op the op 119 /// @return the r 120 /// @throws X the x 121 /// 122 @SuppressWarnings("PMD.ShortVariable") 123 public <R, X extends Throwable> R call(CallableOp<? extends R, X> op) 124 throws X { 125 var carriers = carrierList(); 126 if (carriers == null) { 127 return op.call(); 128 } 129 return carriers.call(op); 130 } 131 132 /// Short for `carriers().run(task)`. 133 /// 134 /// @param task the task 135 /// 136 public void run(Runnable task) { 137 var carriers = carrierList(); 138 if (carriers == null) { 139 task.run(); 140 } else { 141 carriers.run(task); 142 } 143 } 144 } 145 146 private ScopedValueContext() { 147 // Make javadoc happy 148 } 149 150 /// Adds the value to the registry. 151 /// 152 /// @param values the values 153 /// 154 public static void add(ScopedValue<?>... values) { 155 Arrays.asList(values).forEach(registry::add); 156 } 157 158 /// Creates a new snapshot. 159 /// 160 /// @return the snapshot 161 /// 162 public static Snapshot snapshot() { 163 return new Snapshot(); 164 } 165 166 /// Executes the task with the registered scoped values inherited 167 /// from the current thread. 168 /// 169 /// @param <T> the generic type 170 /// @param executor the executor 171 /// @param task the task 172 /// @return the future 173 /// 174 public static <T> Future<T> submitTo(ExecutorService executor, 175 Callable<T> task) { 176 // Capture values 177 Snapshot snapshot = snapshot(); 178 return executor.submit(() -> { 179 return snapshot.call(task::call); 180 }); 181 } 182}