001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–2025 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.construct;
015
016import java.lang.System.Logger;
017
018import java.util.concurrent.CompletableFuture;
019import java.util.concurrent.RunnableFuture;
020
021import java.util.concurrent.atomic.AtomicReference;
022
023import java.util.function.Consumer;
024import java.util.function.Supplier;
025
026import javax.annotation.processing.ProcessingEnvironment;
027
028import static java.lang.System.getLogger;
029
030import static java.lang.System.Logger.Level.ERROR;
031
032/**
033 * A utility class that can {@linkplain #of() supply} a {@link ProcessingEnvironment} suitable for use at runtime.
034 *
035 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
036 *
037 * @see #get()
038 *
039 * @see #of()
040 *
041 * @see #close()
042 *
043 * @see ProcessingEnvironment
044 *
045 * @see Domain
046 */
047public final class RuntimeProcessingEnvironmentSupplier implements AutoCloseable, Supplier<ProcessingEnvironment> {
048
049
050  /*
051   * Static fields.
052   */
053
054
055  private static final Logger LOGGER = getLogger(RuntimeProcessingEnvironmentSupplier.class.getName());
056
057  private static final RuntimeProcessingEnvironmentSupplier INSTANCE = new RuntimeProcessingEnvironmentSupplier();
058
059
060  /*
061   * Instance fields.
062   */
063
064
065  private final AtomicReference<BlockingCompilationTask> r;
066
067
068  /*
069   * Constructors.
070   */
071
072
073  private RuntimeProcessingEnvironmentSupplier() {
074    super();
075    this.r = new AtomicReference<>();
076    install(this.r::set);
077  }
078
079
080  /*
081   * Instance methods.
082   */
083
084
085  /**
086   * Closes this {@link RuntimeProcessingEnvironmentSupplier}, which invalidates all {@link ProcessingEnvironment}s
087   * {@linkplain #get() supplied} by it.
088   *
089   * <p>A subsequent call to {@link #get()} will reset this {@link RuntimeProcessingEnvironmentSupplier}.</p>
090   *
091   * <p>Closing a {@link RuntimeProcessingEnvironmentSupplier} that has already been closed has no effect.</p>
092   *
093   * <p>Most users should not have a need to call this method. {@link RuntimeProcessingEnvironmentSupplier} instances do
094   * not have to be closed.</p>
095   *
096   * @see #get()
097   */
098  @Override // AutoCloseable
099  public final void close() {
100    this.r.get().cancel(true);
101  }
102
103  /**
104   * Returns a non-{@code null}, {@link ProcessingEnvironment} suitable for runtime use.
105   *
106   * <p>{@link ProcessingEnvironment} instances are not guaranteed to be thread-safe.</p>
107   *
108   * @return a non-{@code null} {@link ProcessingEnvironment}
109   *
110   * @see Domain
111   */
112  @Override // Supplier<ProcessingEnvironment>
113  public final ProcessingEnvironment get() {
114    final BlockingCompilationTask f = this.r.get();
115    return
116      (f.isCompletedExceptionally() && f.exceptionNow() instanceof ClosedProcessorException ? install(this.r::set) : f)
117      .join();
118  }
119
120
121  /*
122   * Static methods.
123   */
124
125
126  private static final BlockingCompilationTask install(final Consumer<? super BlockingCompilationTask> c) {
127    final BlockingCompilationTask f = new BlockingCompilationTask();
128    c.accept(f);
129    Thread.ofVirtual()
130      .name(RuntimeProcessingEnvironmentSupplier.class.getName())
131      .uncaughtExceptionHandler((thread, exception) -> {
132          f.completeExceptionally(exception);
133          if (LOGGER.isLoggable(ERROR)) {
134            LOGGER.log(ERROR, exception.getMessage(), exception);
135          }
136        })
137      .start(f);
138    return f;
139  }
140
141  /**
142   * Returns a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}.
143   *
144   * @return a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}
145   *
146   * @see ProcessingEnvironment
147   *
148   * @see Domain
149   */
150  public static final RuntimeProcessingEnvironmentSupplier of() {
151    return INSTANCE;
152  }
153
154}