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}