FilePrintStreamFactory.java

package org.jbehave.core.reporters;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.jbehave.core.io.CodeLocations;
import org.jbehave.core.io.StoryLocation;

/**
 * Creates {@link PrintStream} instances that write to a file identified by the
 * {@link StoryLocation}. {@link FileConfiguration} specifies directory and the
 * extension, providing useful default values.
 */
public class FilePrintStreamFactory implements PrintStreamFactory {

    private final StoryLocation storyLocation;
    private FileConfiguration configuration;
    private File outputFile;

    public FilePrintStreamFactory(StoryLocation storyLocation) {
        this(storyLocation, new FileConfiguration());
    }

    public FilePrintStreamFactory(StoryLocation storyLocation, FileConfiguration configuration) {
        this.storyLocation = storyLocation;
        this.configuration = configuration;
    }

    @Override
    public PrintStream createPrintStream() {
        try {
            outputFile = outputFile();
            outputFile.getParentFile().mkdirs();
            return new FilePrintStream(outputFile, false);
        } catch (Exception e) {
            throw new PrintStreamCreationFailed(outputFile, e);
        }
    }

    public File getOutputFile() {
        return outputFile;
    }

    public void useConfiguration(FileConfiguration configuration) {
        this.configuration = configuration;
        this.outputFile = outputFile();
    }

    public FileConfiguration configuration() {
        return configuration;
    }

    protected File outputFile() {
        return new File(outputDirectory(), outputName());
    }

    /**
     * Return the file output directory, using the configured
     * {@link FilePathResolver}
     * 
     * @return The File representing the output directory
     */
    protected File outputDirectory() {
        return new File(configuration.getPathResolver().resolveDirectory(storyLocation,
                configuration.getRelativeDirectory()));
    }

    /**
     * Return the file output name, using the configured
     * {@link FilePathResolver}
     * 
     * @return The file output name
     */
    protected String outputName() {
        return configuration.getPathResolver().resolveName(storyLocation, configuration.getExtension());
    }

    public static interface FilePathResolver {

        String resolveDirectory(StoryLocation storyLocation, String relativeDirectory);

        String resolveName(StoryLocation storyLocation, String extension);

    }

    /**
     * Resolves directory from code location parent file.
     */
    public abstract static class AbstractPathResolver implements FilePathResolver {

        @Override
        public String resolveDirectory(StoryLocation storyLocation, String relativeDirectory) {
            File parent = new File(CodeLocations.getPathFromURL(storyLocation.getCodeLocation())).getParentFile();
            return parent.getPath().replace('\\', '/') + "/" + relativeDirectory;
        }

    }

    /**
     * Resolves story location path to java packaged name, replacing '/' with '.'
     */
    public static class ResolveToPackagedName extends AbstractPathResolver {

        @Override
        public String resolveName(StoryLocation storyLocation, String extension) {
            String name = storyLocation.getPath().replaceAll(":?/", ".");
            if (name.startsWith(".")) {
                name = name.substring(1);
            }
            return StringUtils.substringBeforeLast(name, ".") + "." + extension;
        }

    }

    /**
     * Resolves story location path to simple name, considering portion after last '/'.
     */
    public static class ResolveToSimpleName extends AbstractPathResolver {

        @Override
        public String resolveName(StoryLocation storyLocation, String extension) {
            String name = storyLocation.getPath();
            if (StringUtils.contains(name, '/')) {
                name = StringUtils.substringAfterLast(name, "/");
            }
            return StringUtils.substringBeforeLast(name, ".") + "." + extension;
        }

    }

    public static class FilePrintStream extends PrintStream {

        private final File outputFile;
        private final boolean append;

        public FilePrintStream(File outputFile, boolean append) throws FileNotFoundException {
            super(new FileOutputStream(outputFile, append));
            this.outputFile = outputFile;
            this.append = append;
        }

        @Override
        public String toString() {
            return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(outputFile).append(append)
                    .toString();
        }

    }

    /**
     * Configuration class for file print streams. Allows specification the
     * relative directory (relative to code location) and file extension.
     * Provides as defaults {@link #RELATIVE_DIRECTORY} and {@link #EXTENSION}.
     */
    public static class FileConfiguration {
        public static final String RELATIVE_DIRECTORY = "jbehave";
        public static final String EXTENSION = "html";

        private final String relativeDirectory;
        private final String extension;
        private final FilePathResolver pathResolver;

        public FileConfiguration() {
            this(EXTENSION);
        }

        public FileConfiguration(String extension) {
            this(RELATIVE_DIRECTORY, extension, new ResolveToPackagedName());
        }

        public FileConfiguration(String relativeDirectory, String extension, FilePathResolver pathResolver) {
            this.relativeDirectory = relativeDirectory;
            this.extension = extension;
            this.pathResolver = pathResolver;
        }

        public String getRelativeDirectory() {
            return relativeDirectory;
        }

        public String getExtension() {
            return extension;
        }

        public FilePathResolver getPathResolver() {
            return pathResolver;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
        }

    }

    @SuppressWarnings("serial")
    public class PrintStreamCreationFailed extends RuntimeException {
        public PrintStreamCreationFailed(File file, Exception cause) {
            super("Failed to create print stream for file " + file, cause);
        }
    }
}