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.filemanagers.JctFileManager; 019import java.util.Deque; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.concurrent.locks.Lock; 023import java.util.concurrent.locks.ReentrantLock; 024import org.apiguardian.api.API; 025import org.apiguardian.api.API.Status; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * A collection representing a chain of configurers to apply to a file manager. 031 * 032 * <p>This is used to provide a set of ordered configuration operations to perform on newly 033 * created file managers to prepare them for consumption in test cases. Common operations 034 * may include: 035 * 036 * <ul> 037 * <li>Automatically configuring the annotation processor paths;</li> 038 * <li>Creating empty locations to prevent compilers raising exceptions;</li> 039 * <li>Installing logging interceptors.</li> 040 * </ul> 041 * 042 * <p>When configuring a file manager with this chain, each configurer is invoked in the 043 * provided order. Configurers themselves must return a file manager as a result of the 044 * configure operation. This operation may return the input file manager if the operation 045 * mutated the input, or it may return a different file manager instance. In the latter 046 * case, subsequent configuration operations internally will use the new file manager. 047 * The last returned file manager in the chain will be used as the final returned result. 048 * This enables configurers to wrap file managers in proxies or delegating implementations 049 * to intercept or override existing behaviours. 050 * 051 * <p>While not designed for concurrent use, all operations are shielded by a 052 * lock to guard against potentially confusing behaviour, should this component be shared across 053 * multiple physical or virtual threads. 054 * 055 * @author Ashley Scopes 056 * @since 0.0.1 057 */ 058@API(since = "0.0.1", status = Status.STABLE) 059public final class JctFileManagerConfigurerChain { 060 061 private static final Logger LOGGER = LoggerFactory.getLogger(JctFileManagerConfigurerChain.class); 062 063 private final Lock lock; 064 private final Deque<JctFileManagerConfigurer> configurers; 065 066 /** 067 * Initialise this chain. 068 */ 069 public JctFileManagerConfigurerChain() { 070 lock = new ReentrantLock(); 071 configurers = new LinkedList<>(); 072 } 073 074 /** 075 * Add a configurer to the start of the chain. 076 * 077 * @param configurer the configurer to add. 078 * @return this chain for further calls. 079 */ 080 public JctFileManagerConfigurerChain addFirst(JctFileManagerConfigurer configurer) { 081 lock.lock(); 082 try { 083 configurers.addFirst(configurer); 084 } finally { 085 lock.unlock(); 086 } 087 return this; 088 } 089 090 /** 091 * Add a configurer to the end of the chain. 092 * 093 * @param configurer the configurer to add. 094 * @return this chain for further calls. 095 */ 096 public JctFileManagerConfigurerChain addLast(JctFileManagerConfigurer configurer) { 097 lock.lock(); 098 try { 099 configurers.addLast(configurer); 100 } finally { 101 lock.unlock(); 102 } 103 return this; 104 } 105 106 /** 107 * Get an immutable copy of the list of configurers. 108 * 109 * @return the list of configurers. 110 */ 111 public List<JctFileManagerConfigurer> list() { 112 lock.lock(); 113 try { 114 return List.copyOf(configurers); 115 } finally { 116 lock.unlock(); 117 } 118 } 119 120 /** 121 * Apply each configurer to the given file manager in order. 122 * 123 * @param fileManager the file manager to configure. 124 * @return the configured file manager to use. This may or may not be the same object as the input 125 * parameter, depending on how the configurers manipulate the input object. 126 */ 127 public JctFileManager configure(JctFileManager fileManager) { 128 lock.lock(); 129 try { 130 for (var configurer : configurers) { 131 if (configurer.isEnabled()) { 132 LOGGER.debug("Applying {} to file manager {}", configurer, fileManager); 133 // Configurers can totally replace the existing file manager 134 // if they choose. 135 fileManager = configurer.configure(fileManager); 136 } else { 137 LOGGER.trace("Skipping {}", configurer); 138 } 139 } 140 } finally { 141 lock.unlock(); 142 } 143 144 return fileManager; 145 } 146}