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}