001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–2026 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 * <p>Most users should simply {@linkplain DefaultDomain#DefaultDomain() use an appropriate <code>DefaultDomain</code>}
033 * instead of working directly with instances of this class.</p>
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 DefaultDomain#DefaultDomain()
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}, <strong>which invalidates all {@link
087   * ProcessingEnvironment}s {@linkplain #get() supplied} by it</strong>.
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><strong>{@link ProcessingEnvironment} instances are not guaranteed to be safe for concurrent use by multiple
107   * threads.</strong></p>
108   *
109   * <p>Most users should simply use an appropriate {@link DefaultDomain} instead of working directly with instances of
110   * this class.</p>
111   *
112   * @return a non-{@code null} {@link ProcessingEnvironment}
113   *
114   * @see DefaultDomain#DefaultDomain()
115   */
116  @Override // Supplier<ProcessingEnvironment>
117  public final ProcessingEnvironment get() {
118    final BlockingCompilationTask f = this.r.get();
119    return
120      (f.isCompletedExceptionally() && f.exceptionNow() instanceof ClosedProcessorException ? install(this.r::set) : f)
121      .join();
122  }
123
124
125  /*
126   * Static methods.
127   */
128
129
130  private static final BlockingCompilationTask install(final Consumer<? super BlockingCompilationTask> c) {
131    final BlockingCompilationTask f = new BlockingCompilationTask();
132    c.accept(f);
133    Thread.ofVirtual()
134      .name(RuntimeProcessingEnvironmentSupplier.class.getName())
135      .uncaughtExceptionHandler((thread, exception) -> {
136          f.completeExceptionally(exception);
137          if (LOGGER.isLoggable(ERROR)) {
138            LOGGER.log(ERROR, exception.getMessage(), exception);
139          }
140        })
141      .start(f);
142    return f;
143  }
144
145  /**
146   * Returns a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}.
147   *
148   * <p>Most users should simply use an appropriate {@link DefaultDomain} instead of working directly with instances of
149   * this class.</p>
150   *
151   * @return a non-{@code null} {@link RuntimeProcessingEnvironmentSupplier}
152   *
153   * @see ProcessingEnvironment
154   *
155   * @see DefaultDomain#DefaultDomain()
156   */
157  public static final RuntimeProcessingEnvironmentSupplier of() {
158    return INSTANCE;
159  }
160
161}