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}