RollingFileOutputBuilder.java

package io.jstach.rainbowgum.rolling;

import io.jstach.rainbowgum.LogProperties;
import io.jstach.rainbowgum.LogProperty;

/**
 * Builder to create {@link io.jstach.rainbowgum.LogProvider<io.jstach.rainbowgum.rolling.RollingFileOutput> }.
 * Creates a rolling file output provider.
 * <table class="table">
 * <caption><strong>Properties retrieved from LogProperties</strong></caption>
 * <tr>
 * <th>Property Pattern</th>
 * <th>Type</th>
 * <th>Required</th>
 * <th>Default</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_fileName }</code></td>
 * <td><code>String</code></td>
 * <td><code>false</code></td>
 * <td><code>null</code></td>
 * <td>File name.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_append }</code></td>
 * <td><code>Boolean</code></td>
 * <td><code>false</code></td>
 * <td><code>null</code></td>
 * <td>Whether or not to append to an existing file when first opened.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_prudent }</code></td>
 * <td><code>Boolean</code></td>
 * <td><code>false</code></td>
 * <td><code>null</code></td>
 * <td>Logback prudent mode where files are locked on each write.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_bufferSize }</code></td>
 * <td><code>String (converted)</code></td>
 * <td><code>true</code></td>
 * <td>{@link io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_BUFFER_SIZE }</td>
 * <td>Buffer size, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 8KB}. Zero means unbuffered.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_maxFileSize }</code></td>
 * <td><code>String (converted)</code></td>
 * <td><code>true</code></td>
 * <td>{@link io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_MAX_FILE_SIZE }</td>
 * <td>Max file size before a roll is triggered, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 10MB}.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_maxHistory }</code></td>
 * <td><code>Integer</code></td>
 * <td><code>true</code></td>
 * <td>{@value io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_MAX_HISTORY }</td>
 * <td>Number of archives to retain.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_totalSizeCap }</code></td>
 * <td><code>String (converted)</code></td>
 * <td><code>true</code></td>
 * <td>{@link io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_TOTAL_SIZE_CAP }</td>
 * <td>Total archive size cap; zero means unlimited.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_cleanHistoryOnStart }</code></td>
 * <td><code>Boolean</code></td>
 * <td><code>true</code></td>
 * <td>{@value io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_CLEAN_HISTORY_ON_START }</td>
 * <td>Whether pruning also runs once at start, not just after each roll.</td>
 * </tr>
 * <tr>
 * <td><code>{@value PROPERTY_fileNamePattern }</code></td>
 * <td><code>String</code></td>
 * <td><code>true</code></td>
 * <td>{@value io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_FILE_NAME_PATTERN }</td>
 * <td>Archive naming pattern; must contain <code>%i</code> and must not contain <code>%d</code> (date based rotation is not supported).</td>
 * </tr>
 * </table>
 */
public final class RollingFileOutputBuilder implements io.jstach.rainbowgum.LogBuilder<RollingFileOutputBuilder,io.jstach.rainbowgum.LogProvider<io.jstach.rainbowgum.rolling.RollingFileOutput>> {

	/**
	 * The properties to be retrieved from config will have
	 * this prefix.
	 */
	static final String PROPERTY_PREFIX = "logging.appender.{name}.output.rolling.";
	
	/**
	 * <code>{@value #PROPERTY_fileName } = java.lang.String</code> File name.
	 */
	static final String PROPERTY_fileName = PROPERTY_PREFIX + "fileName";
	/**
	 * <code>{@value #PROPERTY_append } = java.lang.Boolean</code> Whether or not to append to an existing file when first opened.
	 */
	static final String PROPERTY_append = PROPERTY_PREFIX + "append";
	/**
	 * <code>{@value #PROPERTY_prudent } = java.lang.Boolean</code> Logback prudent mode where files are locked on each write.
	 */
	static final String PROPERTY_prudent = PROPERTY_PREFIX + "prudent";
	/**
	 * <code>{@value #PROPERTY_bufferSize } = io.jstach.rainbowgum.file.DataSize</code> Buffer size, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 8KB}. Zero means unbuffered.
	 */
	static final String PROPERTY_bufferSize = PROPERTY_PREFIX + "bufferSize";
	/**
	 * <code>{@value #PROPERTY_maxFileSize } = io.jstach.rainbowgum.file.DataSize</code> Max file size before a roll is triggered, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 10MB}.
	 */
	static final String PROPERTY_maxFileSize = PROPERTY_PREFIX + "maxFileSize";
	/**
	 * <code>{@value #PROPERTY_maxHistory } = java.lang.Integer</code> Number of archives to retain.
	 */
	static final String PROPERTY_maxHistory = PROPERTY_PREFIX + "maxHistory";
	/**
	 * <code>{@value #PROPERTY_totalSizeCap } = io.jstach.rainbowgum.file.DataSize</code> Total archive size cap; zero means unlimited.
	 */
	static final String PROPERTY_totalSizeCap = PROPERTY_PREFIX + "totalSizeCap";
	/**
	 * <code>{@value #PROPERTY_cleanHistoryOnStart } = java.lang.Boolean</code> Whether pruning also runs once at start, not just after each roll.
	 */
	static final String PROPERTY_cleanHistoryOnStart = PROPERTY_PREFIX + "cleanHistoryOnStart";
	/**
	 * <code>{@value #PROPERTY_fileNamePattern } = java.lang.String</code> Archive naming pattern; must contain <code>%i</code> and must not contain <code>%d</code> (date based rotation is not supported).
	 */
	static final String PROPERTY_fileNamePattern = PROPERTY_PREFIX + "fileNamePattern";
	
	private final String propertyPrefix;
	final String property_fileName;
	final String property_append;
	final String property_prudent;
	final String property_bufferSize;
	final String property_maxFileSize;
	final String property_maxHistory;
	final String property_totalSizeCap;
	final String property_cleanHistoryOnStart;
	final String property_fileNamePattern;
	
	private final java.lang.String name;
	private java.net.@org.jspecify.annotations.Nullable URI uri;
	private java.lang.@org.jspecify.annotations.Nullable String fileName = null;
	private java.lang.@org.jspecify.annotations.Nullable Boolean append = null;
	private java.lang.@org.jspecify.annotations.Nullable Boolean prudent = null;
	private io.jstach.rainbowgum.file.DataSize bufferSize = io.jstach.rainbowgum.rolling.RollingFileOutput.DEFAULT_BUFFER_SIZE;
	private io.jstach.rainbowgum.file.DataSize maxFileSize = io.jstach.rainbowgum.rolling.RollingFileOutput.DEFAULT_MAX_FILE_SIZE;
	private java.lang.Integer maxHistory = io.jstach.rainbowgum.rolling.RollingFileOutput.DEFAULT_MAX_HISTORY;
	private io.jstach.rainbowgum.file.DataSize totalSizeCap = io.jstach.rainbowgum.rolling.RollingFileOutput.DEFAULT_TOTAL_SIZE_CAP;
	private java.lang.Boolean cleanHistoryOnStart = io.jstach.rainbowgum.rolling.RollingFileOutput.DEFAULT_CLEAN_HISTORY_ON_START;
	private java.lang.String fileNamePattern = io.jstach.rainbowgum.rolling.RollingFileOutput.DEFAULT_FILE_NAME_PATTERN;
	
	/**
	 * Create a builder for {@code io.jstach.rainbowgum.LogProvider<io.jstach.rainbowgum.rolling.RollingFileOutput> }.
	 * @param name will fill <code>{name}</code> in <code>logging.appender.{name}.output.rolling.</code>. Name of output, not file name.
	 */
	public RollingFileOutputBuilder(
			java.lang.String name
			) {
		try {
			java.util.Map<String,String> prefixParameters = java.util.Map.of(
					"name", name
				);
			this.propertyPrefix = LogProperties.interpolateKey(PROPERTY_PREFIX, prefixParameters);
			this.name = name;
			property_fileName = LogProperties.interpolateKey(PROPERTY_fileName, prefixParameters);
			property_append = LogProperties.interpolateKey(PROPERTY_append, prefixParameters);
			property_prudent = LogProperties.interpolateKey(PROPERTY_prudent, prefixParameters);
			property_bufferSize = LogProperties.interpolateKey(PROPERTY_bufferSize, prefixParameters);
			property_maxFileSize = LogProperties.interpolateKey(PROPERTY_maxFileSize, prefixParameters);
			property_maxHistory = LogProperties.interpolateKey(PROPERTY_maxHistory, prefixParameters);
			property_totalSizeCap = LogProperties.interpolateKey(PROPERTY_totalSizeCap, prefixParameters);
			property_cleanHistoryOnStart = LogProperties.interpolateKey(PROPERTY_cleanHistoryOnStart, prefixParameters);
			property_fileNamePattern = LogProperties.interpolateKey(PROPERTY_fileNamePattern, prefixParameters);
		}
		catch (IllegalArgumentException e) {
			throw LogProperty.ValidationException.of(this.getClass(), e);
		}
	}


	/**
	 * Sets uri.
	 * File uri, set by the appender from its <code>output.uri</code>.
	 * Default is <code>null</code>.
	 * @param uri not configurable through properties File uri, set by the appender from its <code>output.uri</code>.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder uri(java.net.@org.jspecify.annotations.Nullable URI uri) {
		this.uri = uri;
		return this;
	}

	
	/**
	 * Sets fileName.
	 * File name.
	 * Default is <code>null</code>.
	 * @param fileName <code>{@value #PROPERTY_fileName } = java.lang.String</code> File name.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder fileName(java.lang.@org.jspecify.annotations.Nullable String fileName) {
		this.fileName = fileName;
		return this;
	}
	
	/**
	 * Sets append.
	 * Whether or not to append to an existing file when first opened.
	 * Default is <code>null</code>.
	 * @param append <code>{@value #PROPERTY_append } = java.lang.Boolean</code> Whether or not to append to an existing file when first opened.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder append(java.lang.@org.jspecify.annotations.Nullable Boolean append) {
		this.append = append;
		return this;
	}
	
	/**
	 * Sets prudent.
	 * Logback prudent mode where files are locked on each write.
	 * Default is <code>null</code>.
	 * @param prudent <code>{@value #PROPERTY_prudent } = java.lang.Boolean</code> Logback prudent mode where files are locked on each write.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder prudent(java.lang.@org.jspecify.annotations.Nullable Boolean prudent) {
		this.prudent = prudent;
		return this;
	}
	
	/**
	 * Sets <strong>required</strong> bufferSize.
	 * Buffer size, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 8KB}. Zero means unbuffered.
	 * Default is {@link io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_BUFFER_SIZE }.
	 * @param bufferSize <code>{@value #PROPERTY_bufferSize } = io.jstach.rainbowgum.file.DataSize</code> Buffer size, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 8KB}. Zero means unbuffered.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder bufferSize(io.jstach.rainbowgum.file.DataSize bufferSize) {
		this.bufferSize = bufferSize;
		return this;
	}
	
	/**
	 * Sets <strong>required</strong> maxFileSize.
	 * Max file size before a roll is triggered, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 10MB}.
	 * Default is {@link io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_MAX_FILE_SIZE }.
	 * @param maxFileSize <code>{@value #PROPERTY_maxFileSize } = io.jstach.rainbowgum.file.DataSize</code> Max file size before a roll is triggered, in {@link io.jstach.rainbowgum.file.DataSize#parse(String)} format when set by property, e.g. {@code 10MB}.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder maxFileSize(io.jstach.rainbowgum.file.DataSize maxFileSize) {
		this.maxFileSize = maxFileSize;
		return this;
	}
	
	/**
	 * Sets <strong>required</strong> maxHistory.
	 * Number of archives to retain.
	 * Default is {@value io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_MAX_HISTORY }.
	 * @param maxHistory <code>{@value #PROPERTY_maxHistory } = java.lang.Integer</code> Number of archives to retain.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder maxHistory(java.lang.Integer maxHistory) {
		this.maxHistory = maxHistory;
		return this;
	}
	
	/**
	 * Sets <strong>required</strong> totalSizeCap.
	 * Total archive size cap; zero means unlimited.
	 * Default is {@link io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_TOTAL_SIZE_CAP }.
	 * @param totalSizeCap <code>{@value #PROPERTY_totalSizeCap } = io.jstach.rainbowgum.file.DataSize</code> Total archive size cap; zero means unlimited.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder totalSizeCap(io.jstach.rainbowgum.file.DataSize totalSizeCap) {
		this.totalSizeCap = totalSizeCap;
		return this;
	}
	
	/**
	 * Sets <strong>required</strong> cleanHistoryOnStart.
	 * Whether pruning also runs once at start, not just after each roll.
	 * Default is {@value io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_CLEAN_HISTORY_ON_START }.
	 * @param cleanHistoryOnStart <code>{@value #PROPERTY_cleanHistoryOnStart } = java.lang.Boolean</code> Whether pruning also runs once at start, not just after each roll.
	 * @return this builder.
	 */
	public RollingFileOutputBuilder cleanHistoryOnStart(java.lang.Boolean cleanHistoryOnStart) {
		this.cleanHistoryOnStart = cleanHistoryOnStart;
		return this;
	}
	
	/**
	 * Sets <strong>required</strong> fileNamePattern.
	 * Archive naming pattern; must contain <code>%i</code> and must not contain <code>%d</code> (date based rotation is not supported).
	 * Default is {@value io.jstach.rainbowgum.rolling.RollingFileOutput#DEFAULT_FILE_NAME_PATTERN }.
	 * @param fileNamePattern <code>{@value #PROPERTY_fileNamePattern } = java.lang.String</code> Archive naming pattern; must contain <code>%i</code> and must not contain <code>%d</code> (date based rotation is not supported).
	 * @return this builder.
	 */
	public RollingFileOutputBuilder fileNamePattern(java.lang.String fileNamePattern) {
		this.fileNamePattern = fileNamePattern;
		return this;
	}
	
	/**
	 * Creates {@code io.jstach.rainbowgum.LogProvider<io.jstach.rainbowgum.rolling.RollingFileOutput> } from this builder.
	 * @return {@code io.jstach.rainbowgum.LogProvider<io.jstach.rainbowgum.rolling.RollingFileOutput> }.
	 */
	public io.jstach.rainbowgum.LogProvider<io.jstach.rainbowgum.rolling.RollingFileOutput> build() {
		try {
			return io.jstach.rainbowgum.rolling.RollingFileOutput.of(
					this.name
					, this.uri
					, this.fileName
					, this.append
					, this.prudent
					, io.jstach.rainbowgum.LogProperty.require(property_bufferSize, this.bufferSize)
					, io.jstach.rainbowgum.LogProperty.require(property_maxFileSize, this.maxFileSize)
					, io.jstach.rainbowgum.LogProperty.require(property_maxHistory, this.maxHistory)
					, io.jstach.rainbowgum.LogProperty.require(property_totalSizeCap, this.totalSizeCap)
					, io.jstach.rainbowgum.LogProperty.require(property_cleanHistoryOnStart, this.cleanHistoryOnStart)
					, io.jstach.rainbowgum.LogProperty.require(property_fileNamePattern, this.fileNamePattern)
					);
		}
		catch (IllegalArgumentException | LogProperty.PropertyMissingException e) {
			throw LogProperty.ValidationException.of(this.getClass(), e);
		}
	}
	
	@Override
	public RollingFileOutputBuilder fromProperties(LogProperties properties) {
		var __v = LogProperty.Validator.of(this.getClass());
		var _fileName = properties.forKey(property_fileName).ofString().or(this.fileName).validateIfError(__v);
		var _append = properties.forKey(property_append).ofBoolean().or(this.append).validateIfError(__v);
		var _prudent = properties.forKey(property_prudent).ofBoolean().or(this.prudent).validateIfError(__v);
		var _bufferSize = properties.forKey(property_bufferSize).ofString().map(_v -> io.jstach.rainbowgum.rolling.RollingFileOutput.parseDataSize(_v)).or(this.bufferSize).validate(__v);
		var _maxFileSize = properties.forKey(property_maxFileSize).ofString().map(_v -> io.jstach.rainbowgum.rolling.RollingFileOutput.parseDataSize(_v)).or(this.maxFileSize).validate(__v);
		var _maxHistory = properties.forKey(property_maxHistory).ofInt().or(this.maxHistory).validate(__v);
		var _totalSizeCap = properties.forKey(property_totalSizeCap).ofString().map(_v -> io.jstach.rainbowgum.rolling.RollingFileOutput.parseDataSize(_v)).or(this.totalSizeCap).validate(__v);
		var _cleanHistoryOnStart = properties.forKey(property_cleanHistoryOnStart).ofBoolean().or(this.cleanHistoryOnStart).validate(__v);
		var _fileNamePattern = properties.forKey(property_fileNamePattern).ofString().or(this.fileNamePattern).validate(__v);
		__v.validate();
		this.fileName = _fileName.valueOrNull();
		this.append = _append.valueOrNull();
		this.prudent = _prudent.valueOrNull();
		this.bufferSize = _bufferSize.value();
		this.maxFileSize = _maxFileSize.value();
		this.maxHistory = _maxHistory.value();
		this.totalSizeCap = _totalSizeCap.value();
		this.cleanHistoryOnStart = _cleanHistoryOnStart.value();
		this.fileNamePattern = _fileNamePattern.value();
		return this;
	}
	
	/**
	 * The interpolated property prefix: {@value #PROPERTY_PREFIX}.
	 * @return resolved prefix which should end with a "<code>.</code>".
	 */
	@Override
	public String propertyPrefix() {
		return this.propertyPrefix;
	}
	
}