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.atomic.AtomicReference;
019
020import java.util.function.Consumer;
021import java.util.function.Supplier;
022
023import javax.annotation.processing.ProcessingEnvironment;
024
025import static java.lang.System.getLogger;
026
027import static java.lang.System.Logger.Level.ERROR;
028
029/**
030 * A utility class that can {@linkplain #of() supply} a {@link ProcessingEnvironment} suitable for use at runtime.
031 *
032 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
033 *
034 * @see #get()
035 *
036 * @see #of()
037 *
038 * @see #close()
039 *
040 * @see ProcessingEnvironment
041 *
042 * @see Domain
043 */
044public final class RuntimeProcessingEnvironmentSupplier implements AutoCloseable, Supplier<ProcessingEnvironment> {
045
046
047  /*
048   * Static fields.
049   */
050
051
052  private static final Logger LOGGER = getLogger(RuntimeProcessingEnvironmentSupplier.class.getName());
053
054  private static final RuntimeProcessingEnvironmentSupplier INSTANCE = new RuntimeProcessingEnvironmentSupplier();
055
056
057  /*
058   * Instance fields.
059   */
060
061
062  private final AtomicReference<BlockingCompilationTask> r;
063
064
065  /*
066   * Constructors.
067   */
068
069
070  private RuntimeProcessingEnvironmentSupplier() {
071    super();
072    this.r = new AtomicReference<>();
073    install(this.r::set);
074  }
075
076
077  /*
078   * Instance methods.
079   */
080
081
082  /**
083   * Closes this {@link RuntimeProcessingEnvironmentSupplier}, <strong>which invalidates all {@link
084   * ProcessingEnvironment}s {@linkplain #get() supplied} by it</strong>.
085   *
086   * <p>A subsequent call to {@link #get()} will reset this {@link RuntimeProcessingEnvironmentSupplier}.</p>
087   *
088   * <p>Closing a {@link RuntimeProcessingEnvironmentSupplier} that has already been closed has no effect.</p>
089   *
090   * <p>Most users should not have a need to call this method. {@link RuntimeProcessingEnvironmentSupplier} instances do
091   * not have to be closed.</p>
092   *
093   * @see #get()
094   */
095  @Override // AutoCloseable
096  public final void close() {
097    this.r.get().cancel(true);
098  }
099
100  /**
101   * Returns a non-{@code null}, {@link ProcessingEnvironment} suitable for runtime use.
102   *
103   * <p><strong>{@link ProcessingEnvironment} instances are not guaranteed to be safe for concurrent use by multiple
104   * threads.</strong></p>
105   *
106   * @return a non-{@code null} {@link ProcessingEnvironment}
107   *
108   * @see Domain
109   */
110  @Override // Supplier<ProcessingEnvironment>
111  public final ProcessingEnvironment get() {
112    final BlockingCompilationTask f = this.r.get();
113    return
114      (f.isCompletedExceptionally() && f.exceptionNow() instanceof ClosedProcessorException ? install(this.r::set) : f)
115      .join();
116  }
117
118
119  /*
120   * Static methods.
121   */
122
123
124  private static final BlockingCompilationTask install(final Consumer<? super BlockingCompilationTask> c) {
125    final BlockingCompilationTask f = new BlockingCompilationTask();
126    c.accept(f);
127    Thread.ofVirtual()
128      .name(RuntimeProcessingEnvironmentSupplier.class.getName())
129      .uncaughtExceptionHandler((thread, exception) -> {
130          f.completeExceptionally(exception);
131          if (LOGGER.isLoggable(ERROR)) {
132            LOGGER.log(ERROR, exception.getMessage(), exception);
133          }
134        })
135      .start(f);
136    return f;
137  }
138
139  /**
140   * Returns a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}.
141   *
142   * @return a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}
143   *
144   * @see ProcessingEnvironment
145   *
146   * @see Domain
147   */
148  public static final RuntimeProcessingEnvironmentSupplier of() {
149    return INSTANCE;
150  }
151
152}