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.filemanagers.config;
017
018import io.github.ascopes.jct.compilers.JctCompiler;
019import io.github.ascopes.jct.ex.JctIllegalInputException;
020import io.github.ascopes.jct.filemanagers.AnnotationProcessorDiscovery;
021import io.github.ascopes.jct.filemanagers.JctFileManager;
022import java.util.Map;
023import javax.tools.StandardLocation;
024import org.apiguardian.api.API;
025import org.apiguardian.api.API.Status;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Configurer for a file manager that makes annotation processors in the classpath accessible to the
031 * annotation processor path.
032 *
033 * <p>If annotation processor discovery is disabled for dependencies, this will be skipped.
034 *
035 * @author Ashley Scopes
036 * @since 0.0.1
037 */
038@API(since = "0.0.1", status = Status.STABLE)
039public final class JctFileManagerAnnotationProcessorClassPathConfigurer
040    implements JctFileManagerConfigurer {
041
042  private static final Logger log = LoggerFactory
043      .getLogger(JctFileManagerAnnotationProcessorClassPathConfigurer.class);
044
045  private static final Map<StandardLocation, StandardLocation> INHERITED_AP_PATHS = Map.of(
046      // https://stackoverflow.com/q/53084037
047      // Seems that javac will always use the classpath to implement this behaviour, and never
048      // the module path. Let's keep this simple and mimic this behaviour. If someone complains
049      // about it being problematic in the future, then I am open to change how this works to
050      // keep it sensible.
051      // (from -> to)
052      StandardLocation.CLASS_PATH, StandardLocation.ANNOTATION_PROCESSOR_PATH
053  );
054
055  private final JctCompiler compiler;
056
057  /**
058   * Initialise the configurer with the desired compiler.
059   *
060   * @param compiler the compiler to wrap.
061   */
062  public JctFileManagerAnnotationProcessorClassPathConfigurer(JctCompiler compiler) {
063    this.compiler = compiler;
064  }
065
066  @Override
067  public JctFileManager configure(JctFileManager fileManager) {
068    log.debug("Configuring annotation processor discovery mechanism");
069
070    switch (compiler.getAnnotationProcessorDiscovery()) {
071      case ENABLED:
072        log.trace("Annotation processor discovery is enabled, ensuring empty location exists");
073
074        INHERITED_AP_PATHS.values().forEach(fileManager::createEmptyLocation);
075
076        return fileManager;
077
078      case INCLUDE_DEPENDENCIES:
079        log.trace("Annotation processor discovery is enabled, copying classpath dependencies "
080            + "into the annotation processor path");
081
082        INHERITED_AP_PATHS.forEach(fileManager::copyContainers);
083        INHERITED_AP_PATHS.values().forEach(fileManager::createEmptyLocation);
084
085        return fileManager;
086
087      default:
088        throw new JctIllegalInputException("Cannot configure annotation processor discovery");
089    }
090  }
091
092  @Override
093  public boolean isEnabled() {
094    return compiler.getAnnotationProcessorDiscovery() != AnnotationProcessorDiscovery.DISABLED;
095  }
096}