All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.spotless.maven.incremental.FileIndex Maven / Gradle / Ivy

There is a newer version: 2.44.0.BETA4
Show newest version
/*
 * Copyright 2021 DiffPlug
 *
 * Licensed 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 com.diffplug.spotless.maven.incremental;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.newBufferedReader;
import static java.nio.file.Files.newBufferedWriter;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.annotation.Nullable;

import org.apache.maven.plugin.logging.Log;

import com.diffplug.common.annotations.VisibleForTesting;

class FileIndex {

	private static final String SEPARATOR = " ";

	private final Path indexFile;
	private final PluginFingerprint pluginFingerprint;
	private final Map fileToLastModifiedTime;
	private final Path projectDir;

	private boolean modified;

	private FileIndex(Path indexFile, PluginFingerprint pluginFingerprint, Map fileToLastModifiedTime, Path projectDir, boolean needsRewrite) {
		this.indexFile = indexFile;
		this.pluginFingerprint = pluginFingerprint;
		this.fileToLastModifiedTime = fileToLastModifiedTime;
		this.projectDir = projectDir;
		this.modified = needsRewrite;
	}

	static FileIndex read(FileIndexConfig config, Log log) {
		Path indexFile = config.getIndexFile();
		if (Files.notExists(indexFile)) {
			log.info("Index file does not exist. Fallback to an empty index");
			return emptyIndexFallback(config);
		}

		try (BufferedReader reader = newBufferedReader(indexFile, UTF_8)) {
			String firstLine = reader.readLine();
			if (firstLine == null) {
				log.info("Index file is empty. Fallback to an empty index");
				return emptyIndexFallback(config);
			}

			PluginFingerprint computedFingerprint = config.getPluginFingerprint();
			PluginFingerprint storedFingerprint = PluginFingerprint.from(firstLine);
			if (!computedFingerprint.equals(storedFingerprint)) {
				log.info("Fingerprint mismatch in the index file. Fallback to an empty index");
				return emptyIndexFallback(config);
			} else {
				Content content = readIndexContent(reader, config.getProjectDir(), log);
				return new FileIndex(indexFile, computedFingerprint, content.fileToLastModifiedTime, config.getProjectDir(), content.needsRewrite);
			}
		} catch (IOException e) {
			log.warn("Error reading the index file. Fallback to an empty index", e);
			return emptyIndexFallback(config);
		}
	}

	static void delete(FileIndexConfig config, Log log) {
		Path indexFile = config.getIndexFile();
		boolean deleted = false;
		try {
			deleted = Files.deleteIfExists(indexFile);
		} catch (IOException e) {
			log.warn("Unable to delete the index file: " + indexFile, e);
		}
		if (deleted) {
			log.info("Deleted the index file: " + indexFile);
		}
	}

	@Nullable
	Instant getLastModifiedTime(Path file) {
		if (!file.startsWith(projectDir)) {
			return null;
		}
		Path relativeFile = projectDir.relativize(file);
		return fileToLastModifiedTime.get(relativeFile);
	}

	void setLastModifiedTime(Path file, Instant time) {
		Path relativeFile = projectDir.relativize(file);
		fileToLastModifiedTime.put(relativeFile, time);
		modified = true;
	}

	@VisibleForTesting
	int size() {
		return fileToLastModifiedTime.size();
	}

	void write() {
		if (!modified) {
			return;
		}

		ensureParentDirExists();
		try (PrintWriter writer = new PrintWriter(newBufferedWriter(indexFile, UTF_8, CREATE, TRUNCATE_EXISTING))) {
			writer.println(pluginFingerprint.value());

			for (Entry entry : fileToLastModifiedTime.entrySet()) {
				writer.println(entry.getKey() + SEPARATOR + entry.getValue());
			}
		} catch (IOException e) {
			throw new UncheckedIOException("Unable to write the index", e);
		}
	}

	private void ensureParentDirExists() {
		Path parentDir = indexFile.getParent();
		if (parentDir == null) {
			throw new IllegalStateException("Index file does not have a parent dir: " + indexFile);
		}
		try {
			Files.createDirectories(parentDir);
		} catch (IOException e) {
			throw new UncheckedIOException("Unable to create parent directory for the index file: " + indexFile, e);
		}
	}

	private static Content readIndexContent(BufferedReader reader, Path projectDir, Log log) throws IOException {
		Map fileToLastModifiedTime = new TreeMap<>();
		boolean needsRewrite = false;

		String line;
		while ((line = reader.readLine()) != null) {
			int separatorIndex = line.lastIndexOf(SEPARATOR);
			if (separatorIndex == -1) {
				throw new IOException("Incorrect index file. No separator found in '" + line + "'");
			}

			Path relativeFile = Paths.get(line.substring(0, separatorIndex));
			Path absoluteFile = projectDir.resolve(relativeFile);
			if (Files.notExists(absoluteFile)) {
				log.info("File stored in the index does not exist: " + relativeFile);
				needsRewrite = true;
			} else {
				Instant lastModifiedTime = parseLastModifiedTime(line, separatorIndex);
				fileToLastModifiedTime.put(relativeFile, lastModifiedTime);
			}
		}

		return new Content(fileToLastModifiedTime, needsRewrite);
	}

	private static Instant parseLastModifiedTime(String line, int separatorIndex) throws IOException {
		try {
			return Instant.parse(line.substring(separatorIndex + 1));
		} catch (DateTimeParseException e) {
			throw new IOException("Incorrect index file. Unable to parse last modified time from '" + line + "'", e);
		}
	}

	private static FileIndex emptyIndexFallback(FileIndexConfig config) {
		return new FileIndex(config.getIndexFile(), config.getPluginFingerprint(), new TreeMap<>(), config.getProjectDir(), true);
	}

	private static class Content {
		final Map fileToLastModifiedTime;
		final boolean needsRewrite;

		Content(Map fileToLastModifiedTime, boolean needsRewrite) {
			this.fileToLastModifiedTime = fileToLastModifiedTime;
			this.needsRewrite = needsRewrite;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy