FileManager.java

/*
 * Copyright 2015-2016 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package net.morimekta.providence.generator.util;

import net.morimekta.util.FileUtil;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;

/**
 * @author Stein Eldar Johnsen
 * @since 19.09.15
 */
public class FileManager {
    private final Path root;

    private final Set<Path>       generatedFiles;
    private final Map<Path, Long> modificationTimeMap;

    public FileManager(Path root) {
        this.root = root;
        this.generatedFiles = new HashSet<>();
        this.modificationTimeMap = new HashMap<>();
    }

    @FunctionalInterface
    public interface CreateHandler {
        void create(OutputStream out) throws IOException;
    }

    public void createIfMissingOrOutdated(Path programFile,
                                          String subPath,
                                          String fileName,
                                          CreateHandler create) throws IOException {
        Path outputFile = absolutePath(subPath, fileName);
        if (Files.exists(outputFile)) {
            long programModified = getLastModifiedTime(programFile);
            long outputModified = getLastModifiedTime(outputFile);
            if (programModified < outputModified) {
                return;
            }
        }

        try (OutputStream out = createInternal(outputFile);
             BufferedOutputStream buf = new BufferedOutputStream(out)) {
            try {
                create.create(buf);
            } finally {
                buf.flush();
            }
        }
    }

    private long getLastModifiedTime(Path path) throws IOException {
        try {
            return modificationTimeMap.computeIfAbsent(path, p -> {
                try {
                    return Files.getLastModifiedTime(path).toMillis();
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        } catch (UncheckedIOException e) {
            throw e.getCause();
        }
    }

    private Path relativePath(Path path) {
        return path.relativize(root);
    }

    private Path absolutePath(String path, String name) throws IOException {
        Path tmp = root;
        if (path != null) {
            tmp = tmp.resolve(path);
        }
        tmp = tmp.resolve(name);
        return FileUtil.readCanonicalPath(tmp);
    }

    private OutputStream createInternal(Path file) throws IOException {
        if (generatedFiles.contains(file)) {
            throw new IOException("File " + relativePath(file) + " already created.");
        }

        Files.createDirectories(file.getParent());
        generatedFiles.add(file);
        return Files.newOutputStream(file, WRITE, CREATE, TRUNCATE_EXISTING);
    }
}