001/*
002 * Copyright (C) 2022 - 2024, the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * 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
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.github.ascopes.jct.compilers;
017
018import org.apiguardian.api.API;
019import org.apiguardian.api.API.Status;
020
021/**
022 * Function representing a configuration operation that can be applied to a compiler.
023 *
024 * <p>This can allow encapsulating common configuration logic across tests into a single place.
025 *
026 * <p>Implementations of this interface should declare the exception type that the implementation
027 * can throw when invoked. If this is not a checked exception, or no case exists where an exception
028 * could be thrown, then this can be set to {@link RuntimeException}.
029 *
030 * <p>The following demonstrates an example usage of this interface. The implementation configures
031 * a specific annotation processor and sets an annotation processor flag.
032 *
033 * <pre><code>
034 * class MyAnnotationProcessorConfigurer implements JctCompilerConfigurer&lt;RuntimeException&gt; {
035 *    {@literal @Override}
036 *    public void configure(JctCompiler compiler) {
037 *      compiler
038 *          .addAnnotationProcessors(new MyAnnotationProcessor())
039 *          .addAnnotationProcessorOptions("MyAnnotationProcessor.debug=true");
040 *    }
041 * }
042 * </code></pre>
043 *
044 * <p>...tests can then make use of this configurer directly:
045 *
046 * <pre><code>
047 *    {@literal @Test}
048 *    void theCompilationSucceedsAsExpected() {
049 *      try (var workspace = Workspaces.newWorkspace()) {
050 *        // Given
051 *        ...
052 *
053 *        var compiler = JctCompilers
054 *            .newPlatformCompiler()
055 *            .release(17)
056 *            .configure(new MyAnnotationProcessorConfigurer());
057 *
058 *        // When
059 *        var compilation = compiler.compile(workspace);
060 *
061 *        // Then
062 *        ...
063 *      }
064 *    }
065 * </code></pre>
066 *
067 * <p>Since this is a functional interface, configurers can be lambda expressions, anonymous
068 * objects, or method references.
069 *
070 * <pre><code>
071 *   compiler
072 *      .configure(c -&gt; c.release(11))
073 *      .configure(this::configureFailures);
074 * </code></pre>
075 *
076 * <p>The JUnit support allows for specifying these configurers in an annotation instead. This
077 * will apply the configurer before passing it to the test as a parameter:
078 *
079 * <pre><code>
080 *   {@literal @JavacCompilersTest(configurers = {MyAnnotationProcessorConfigurer.class})}
081 *   void theCompilationSucceedsAsExpected(JctCompiler compiler) {
082 *     // ...
083 *   }
084 * </code></pre>
085 *
086 * <p>Note that in this case, the configurer must be an outer class or a static nested class
087 * rather than a Lambda expression, anonymous class, class instance, or nested class. It must also
088 * have a single public no-arguments constructor in order to be accessible.
089 *
090 * @param <E> the exception that may be thrown by the configurer.
091 * @author Ashley Scopes
092 * @since 0.0.1
093 */
094@API(since = "0.0.1", status = Status.STABLE)
095@FunctionalInterface
096public interface JctCompilerConfigurer<E extends Exception> {
097
098  /**
099   * Apply configuration logic to the given compiler.
100   *
101   * @param compiler the compiler.
102   * @throws E any exception that may be thrown by the configurer.
103   */
104  void configure(JctCompiler compiler) throws E;
105}