Newer
Older
gradle-lectures / buildSrc / src / main / java / nz / stanger / lecture / GenerateImageFromSourceTask.java
package nz.stanger.lecture;

import groovy.lang.Closure;
import java.util.Set;
import org.gradle.api.DefaultTask;
import org.gradle.api.NonNullApi;
import org.gradle.api.file.ConfigurableFileTree;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileTreeElement;
import org.gradle.api.provider.Property;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.IgnoreEmptyDirectories;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.util.PatternFilterable;
import org.gradle.work.DisableCachingByDefault;
import org.gradle.work.Incremental;

/**
 * Custom task to generate images from text-based source formats, like PlantUML
 * or SVG. Apparently we shouldn't extend {@code SourceTask}, as Gradle wants to
 * move away from extending task types and SourceTask is at some point going to
 * disappear:
 * {@link https://github.com/gradle/gradle/issues/11461#issuecomment-560392782}
 *
 * This is based on how {@code SourceTask} does things, but without the instance
 * variables, as they aren't needed and make things more complex than necessary.
 * {@link https://github.com/gradle/gradle/blob/master/subprojects/core/src/main/java/org/gradle/api/tasks/SourceTask.java}
 *
 * In particular, {@code ConvertImageTask} implements {@code PatternFilterable}
 * so that it can have consistent {@code include} and {@code exclude} methods.
 * We don't need the separate internal {@code PatternSet} that
 * {@code SourceTask} has because we're using {@code ConfigurableFileTree}
 * (which implements {@code PatternFilterable}) instead of
 * {@code ConfigurableFileCollection}.
 *
 * @see org.gradle.api.file.ConfigurableFileTree
 * @see org.gradle.api.tasks.SourceTask
 * @see org.gradle.api.tasks.util.PatternFilterable
 */
@NonNullApi
@DisableCachingByDefault(because = "Super-class, not to be instantiated directly")
abstract public class GenerateImageFromSourceTask extends DefaultTask implements PatternFilterable {

    /**
     * Bitmap image transcoder hint: DPI of output image. 200 dpi looks
     * reasonable. {@link https://stackoverflow.com/a/63684055}
     */
    public static final Integer OUTPUT_DPI = 200;

    /**
     * Bitmap image transcoder hint: scaled resolution of output image.
     * {@link https://stackoverflow.com/a/63684055}
     */
    public static final Float SCALE_BY_RESOLUTION = OUTPUT_DPI / 72f;

    /**
     * Bitmap image transcoder hint: scaled pixel size of output image.
     * {@link https://stackoverflow.com/a/63684055}
     */
    public static final Float PIXEL_UNIT_TO_MM = 25.4f / OUTPUT_DPI;

    /**
     * Name of the FOP configuration file.
     */
    public static final String FOP_CONFIG_FILENAME = "fopconfig.xml";

    /**
     * Output format of the generated image.
     */
    private final Property<ImageFormat> outputFormat = getProject().getObjects().property(ImageFormat.class);

    /**
     * Returns the source for this task. Ignores source files which do not
     * exist.
     *
     * @return the source
     */
    @InputFiles
    @IgnoreEmptyDirectories
    @PathSensitive(PathSensitivity.NAME_ONLY)
    @Incremental
    abstract public ConfigurableFileTree getSource();

    /**
     * Returns the output directory for this task.
     *
     * @return the output directory
     */
    @OutputDirectory
    abstract public DirectoryProperty getOutputDir();

    /**
     * Returns the output format for this task.
     *
     * @return the output format
     */
    @Internal
    public Property<ImageFormat> getOutputFormat() {
        return outputFormat;
    }

    /**
     * Sets the output format for this task.
     *
     * @param format a string value that matches the {@code ImageFormat} enum
     * @see stanger.nz.lecture.ImageFormat
     */
    public void setOutputFormat(String format) {
        outputFormat.set(ImageFormat.valueOf(format.toUpperCase()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable include(String... includes) {
        getSource().include(includes);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable include(Iterable<String> includes) {
        getSource().include(includes);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable include(Spec<FileTreeElement> includeSpec) {
        getSource().include(includeSpec);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable include(Closure includeSpec) {
        getSource().include(includeSpec);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable exclude(String... excludes) {
        getSource().exclude(excludes);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable exclude(Iterable<String> excludes) {
        getSource().exclude(excludes);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable exclude(Spec<FileTreeElement> excludeSpec) {
        getSource().exclude(excludeSpec);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public GenerateImageFromSourceTask exclude(Closure excludeSpec) {
        getSource().exclude(excludeSpec);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Internal
    @Override
    public Set<String> getIncludes() {
        return getSource().getIncludes();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable setIncludes(Iterable<String> includes) {
        getSource().setIncludes(includes);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Internal
    @Override
    public Set<String> getExcludes() {
        return getSource().getExcludes();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public PatternFilterable setExcludes(Iterable<String> excludes) {
        getSource().setExcludes(excludes);
        return this;
    }

}