001/*
002 * Hypo, an extensible and pluggable Java bytecode analytical model.
003 *
004 * Copyright (C) 2021  Kyle Wood (DemonWav)
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the Lesser GNU General Public License as published by
008 * the Free Software Foundation, version 3 of the License only.
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 Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package com.demonwav.hypo.core;
020
021import com.demonwav.hypo.model.ClassDataDecorator;
022import com.demonwav.hypo.model.ClassDataProvider;
023import com.demonwav.hypo.model.ClassDataProviderSet;
024import com.demonwav.hypo.model.HypoModelUtil;
025import com.google.errorprone.annotations.CanIgnoreReturnValue;
026import com.google.errorprone.annotations.concurrent.LazyInit;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.List;
031import java.util.concurrent.ExecutorService;
032import java.util.concurrent.Executors;
033import org.jetbrains.annotations.Contract;
034import org.jetbrains.annotations.NotNull;
035import org.jetbrains.annotations.Nullable;
036
037/**
038 * Core context for Hypo executions. A context contains 4 major components:
039 *
040 * <ol>
041 *     <li>The core {@link ClassDataProvider provider} - The classes to analyze</li>
042 *     <li>The context {@link ClassDataProvider provider} - Additional classes on the classpath for completing the
043 *         analysis of the core provider</li>
044 *     <li>The current {@link HypoConfig configuration} - Global configuration values</li>
045 *     <li>The current {@link ExecutorService executor} - The shared executor to use for all multi-threaded work</li>
046 * </ol>
047 *
048 * <p>When a context is closed both {@link ClassDataProvider providers} are closed and the
049 * {@link ExecutorService executor} is shut down. The context should never be closed while processes in the executor are
050 * still running. To put that another way, any operations which use the executor should wait for all submitted jobs to
051 * complete before continuing.
052 *
053 * <p>Create new instances with {@link #builder()}.
054 */
055public final class HypoContext implements AutoCloseable {
056
057    private final @NotNull HypoConfig config;
058
059    private final @NotNull ClassDataProvider provider;
060    private final @NotNull ClassDataProvider contextProvider;
061
062    @LazyInit private @Nullable ExecutorService executor = null;
063
064    /**
065     * Create a new instance of {@link HypoContext}. Use {@link #builder()} instead.
066     *
067     * @param config The {@link HypoConfig config} to use for this context.
068     * @param provider The {@link ClassDataProvider provider} to use as the core provider to analyze.
069     * @param contextProvider The {@link ClassDataProvider provider} to use as the context provider.
070     */
071    HypoContext(
072        final @NotNull HypoConfig config,
073        final @NotNull ClassDataProvider provider,
074        final @NotNull ClassDataProvider contextProvider
075    ) {
076        this.config = config;
077        this.provider = provider;
078        this.contextProvider = contextProvider;
079    }
080
081    /**
082     * Returns the {@link HypoConfig config}.
083     * @return The {@link HypoConfig config}.
084     */
085    public @NotNull HypoConfig getConfig() {
086        return this.config;
087    }
088
089    /**
090     * Returns the core {@link ClassDataProvider provider}.
091     * @return The core {@link ClassDataProvider provider}.
092     */
093    public @NotNull ClassDataProvider getProvider() {
094        return this.provider;
095    }
096
097    /**
098     * Returns the context {@link ClassDataProvider provider}.
099     * @return The context {@link ClassDataProvider provider}.
100     */
101    public @NotNull ClassDataProvider getContextProvider() {
102        return this.contextProvider;
103    }
104
105    /**
106     * Returns the current {@link ExecutorService executor}, if there is one. If there isn't one already created, a new
107     * executor will be created according to the {@link #getConfig() configuration} and returned. This executor will be
108     * used for all subsequent requests until this context is {@link #close() closed}.
109     *
110     * <p>This method is thread safe: Multiple threads may call it concurrently and they will all receive the same
111     * executor. Concurrent accesses to this method while the executor is being created will all receive the same
112     * executor.
113     *
114     * @return The current {@link ExecutorService executor}.
115     */
116    public @NotNull ExecutorService getExecutor() {
117        ExecutorService e = this.executor;
118        if (e != null) {
119            return e;
120        }
121
122        synchronized (this) {
123            e = this.executor;
124            if (e != null) {
125                return e;
126            }
127
128            if (this.config.getParallelism() <= 0) {
129                e = Executors.newWorkStealingPool();
130            } else {
131                e = Executors.newWorkStealingPool(this.config.getParallelism());
132            }
133            this.executor = e;
134            return e;
135        }
136    }
137
138    @Override
139    public void close() throws Exception {
140        final ExecutorService exec = this.executor;
141        if (exec != null) {
142            // close() should never be called while there are still tasks processing
143            exec.shutdownNow();
144            this.executor = null;
145        }
146
147        Exception thrown = null;
148        try {
149            this.provider.close();
150        } catch (final Exception e) {
151            thrown = e;
152        }
153        try {
154            this.contextProvider.close();
155        } catch (final Exception e) {
156            thrown = HypoModelUtil.addSuppressed(thrown, e);
157        }
158
159        if (thrown != null) {
160            throw thrown;
161        }
162    }
163
164    /**
165     * Create a new {@link Builder builder} for creating new instances of {@link HypoContext}.
166     *
167     * @return A new {@link Builder} instance.
168     */
169    @Contract("-> new")
170    public static @NotNull HypoContext.Builder builder() {
171        return new Builder();
172    }
173
174    /**
175     * Builder class for creating new instances of {@link HypoContext}. Create new instances of this builder with
176     * {@link HypoContext#builder()}.
177     *
178     * <p>This class is structured as the write-only version of {@link HypoContext}, which is read-only.
179     */
180    public static final class Builder {
181
182        private @Nullable HypoConfig config;
183
184        private final @NotNull List<@NotNull ClassDataProvider> providers = new ArrayList<>();
185        private final @NotNull List<@NotNull ClassDataProvider> contextProviders = new ArrayList<>();
186
187        /**
188         * Constructor for {@link Builder}. Use {@link HypoContext#builder()} instead.
189         */
190        Builder() {}
191
192        /**
193         * Add the given {@link ClassDataProvider provider} as a core provider to analyze.
194         *
195         * @param provider The provider to add to the set of core providers.
196         * @return {@code this} for chaining.
197         */
198        @CanIgnoreReturnValue
199        @Contract(value = "_ -> this", mutates = "this")
200        public @NotNull HypoContext.Builder withProvider(final @NotNull ClassDataProvider provider) {
201            this.providers.add(provider);
202            return this;
203        }
204
205        /**
206         * Add multiple {@link ClassDataProvider providers} as core providers to analyze.
207         *
208         * @param providers The providers to add to the set of core providers.
209         * @return {@code this} for chaining.
210         */
211        @CanIgnoreReturnValue
212        @Contract(value = "_ -> this", mutates = "this")
213        public @NotNull HypoContext.Builder withProviders(final @NotNull ClassDataProvider @NotNull ... providers) {
214            this.providers.addAll(Arrays.asList(providers));
215            return this;
216        }
217
218        /**
219         * Add a {@link Collection collection} of {@link ClassDataProvider provider} as core providers to analyze.
220         *
221         * @param providers The providers to add to the set of core providers.
222         * @return {@code this} for chaining.
223         */
224        @CanIgnoreReturnValue
225        @Contract(value = "_ -> this", mutates = "this")
226        public @NotNull HypoContext.Builder withProviders(
227            final @NotNull Collection<@NotNull ? extends ClassDataProvider> providers
228        ) {
229            this.providers.addAll(providers);
230            return this;
231        }
232
233        /**
234         * Clear the current set of core {@link ClassDataProvider providers}.
235         *
236         * @return {@code this} for chaining.
237         */
238        @CanIgnoreReturnValue
239        @Contract(value = "-> this", mutates = "this")
240        public @NotNull HypoContext.Builder clearProviders() {
241            this.providers.clear();
242            return this;
243        }
244
245        /**
246         * Add the given {@link ClassDataProvider provider} as a context provider.
247         *
248         * @param provider The provider to add to the set of context providers.
249         * @return {@code this} for chaining.
250         */
251        @CanIgnoreReturnValue
252        @Contract(value = "_ -> this", mutates = "this")
253        public @NotNull HypoContext.Builder withContextProvider(final @NotNull ClassDataProvider provider) {
254            this.contextProviders.add(provider);
255            return this;
256        }
257
258        /**
259         * Add multiple {@link ClassDataProvider providers} as context providers.
260         *
261         * @param providers The providers to add to the set of context providers.
262         * @return {@code this} for chaining.
263         */
264        @CanIgnoreReturnValue
265        @Contract(value = "_ -> this", mutates = "this")
266        public @NotNull HypoContext.Builder withContextProviders(
267            final @NotNull ClassDataProvider @NotNull ... providers
268        ) {
269            this.contextProviders.addAll(Arrays.asList(providers));
270            return this;
271        }
272
273        /**
274         * Add a {@link Collection collection} of {@link ClassDataProvider provider} as context providers.
275         *
276         * @param providers The providers to add to the set of context providers.
277         * @return {@code this} for chaining.
278         */
279        @CanIgnoreReturnValue
280        @Contract(value = "_ -> this", mutates = "this")
281        public @NotNull HypoContext.Builder withContextProviders(
282            final @NotNull Collection<@NotNull ? extends ClassDataProvider> providers
283        ) {
284            this.contextProviders.addAll(providers);
285            return this;
286        }
287
288        /**
289         * Clear the current set of context {@link ClassDataProvider providers}.
290         *
291         * @return {@code this} for chaining.
292         */
293        @CanIgnoreReturnValue
294        @Contract(value = "-> this", mutates = "this")
295        public @NotNull HypoContext.Builder clearContextProviders() {
296            this.contextProviders.clear();
297            return this;
298        }
299
300        /**
301         * Set the {@link HypoConfig config} for the context to be built.
302         *
303         * <p>This defaults to:
304         *
305         * <pre>
306         *     HypoConfig.builder().build()
307         * </pre>
308         *
309         * @param config The {@link HypoConfig config} to set.
310         * @return {@code this} for chaining.
311         */
312        @CanIgnoreReturnValue
313        @Contract(value = "_ -> this", mutates = "this")
314        public @NotNull HypoContext.Builder withConfig(final @NotNull HypoConfig config) {
315            this.config = config;
316            return this;
317        }
318
319        /**
320         * Use the current values of this builder to create a new instance of {@link HypoContext} and return it.
321         *
322         * @return The new instance of {@link HypoContext} using the values set in this builder.
323         */
324        @Contract(value = "-> new", pure = true)
325        public @NotNull HypoContext build() {
326            HypoConfig conf = this.config;
327            if (conf == null) {
328                conf = HypoConfig.builder().build();
329            }
330
331            for (final ClassDataProvider provider : this.providers) {
332                provider.setContextClassProvider(false);
333            }
334            for (final ClassDataProvider provider : this.contextProviders) {
335                provider.setContextClassProvider(true);
336            }
337
338            final ArrayList<ClassDataProvider> provs = new ArrayList<>(this.providers);
339            provs.addAll(this.contextProviders);
340
341            final ClassDataProviderSet allProvider = ClassDataProviderSet.wrap(provs);
342            final ClassDataDecorator decorator = conf.getDecorator().apply(allProvider);
343            allProvider.setDecorator(decorator);
344
345            final ClassDataProviderSet targetProvider = ClassDataProviderSet.wrap(this.providers);
346            targetProvider.setDecorator(decorator);
347
348            return new HypoContext(conf, targetProvider, allProvider);
349        }
350    }
351}