FileElement.java
/*
* Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.diffmergetool;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import org.eclipse.jgit.diff.DiffEntry;
/**
* The element used as left or right file for compare.
*
*/
public class FileElement {
/**
* The file element type.
*
*/
public enum Type {
/**
* The local file element (ours).
*/
LOCAL,
/**
* The remote file element (theirs).
*/
REMOTE,
/**
* The merged file element (path in worktree).
*/
MERGED,
/**
* The base file element (of ours and theirs).
*/
BASE,
/**
* The backup file element (copy of merged / conflicted).
*/
BACKUP
}
private final String path;
private final Type type;
private final File workDir;
private InputStream stream;
private File tempFile;
/**
* Creates file element for path.
*
* @param path
* the file path
* @param type
* the element type
*/
public FileElement(String path, Type type) {
this(path, type, null);
}
/**
* Creates file element for path.
*
* @param path
* the file path
* @param type
* the element type
* @param workDir
* the working directory of the path (can be null, then current
* working dir is used)
*/
public FileElement(String path, Type type, File workDir) {
this(path, type, workDir, null);
}
/**
* @param path
* the file path
* @param type
* the element type
* @param workDir
* the working directory of the path (can be null, then current
* working dir is used)
* @param stream
* the object stream to load and write on demand, @see getFile(),
* to tempFile once (can be null)
*/
public FileElement(String path, Type type, File workDir,
InputStream stream) {
this.path = path;
this.type = type;
this.workDir = workDir;
this.stream = stream;
}
/**
* @return the file path
*/
public String getPath() {
return path;
}
/**
* @return the element type
*/
public Type getType() {
return type;
}
/**
* Return
* <ul>
* <li>a temporary file if already created and stream is not valid</li>
* <li>OR a real file from work tree: if no temp file was created (@see
* createTempFile()) and if no stream was set</li>
* <li>OR an empty temporary file if path is "/dev/null"</li>
* <li>OR a temporary file with stream content if stream is valid (not
* null); stream is closed and invalidated (set to null) after write to temp
* file, so stream is used only once during first call!</li>
* </ul>
*
* @return the object stream
* @throws IOException
*/
public File getFile() throws IOException {
// if we have already temp file and no stream
// then just return this temp file (it was filled from outside)
if ((tempFile != null) && (stream == null)) {
return tempFile;
}
File file = new File(workDir, path);
// if we have a stream or file is missing (path is "/dev/null")
// then optionally create temporary file and fill it with stream content
if ((stream != null) || isNullPath()) {
if (tempFile == null) {
tempFile = getTempFile(file, type.name(), null);
}
if (stream != null) {
copyFromStream(tempFile, stream);
}
// invalidate the stream, because it is used once
stream = null;
return tempFile;
}
return file;
}
/**
* Check if path id "/dev/null"
*
* @return true if path is "/dev/null"
*/
public boolean isNullPath() {
return path.equals(DiffEntry.DEV_NULL);
}
/**
* Create temporary file in given or system temporary directory.
*
* @param directory
* the directory for the file (can be null); if null system
* temporary directory is used
* @return temporary file in directory or in the system temporary directory
* @throws IOException
*/
public File createTempFile(File directory) throws IOException {
if (tempFile == null) {
tempFile = getTempFile(new File(path), type.name(), directory);
}
return tempFile;
}
/**
* Delete and invalidate temporary file if necessary.
*/
public void cleanTemporaries() {
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}
tempFile = null;
}
/**
* Replace variable in input.
*
* @param input
* the input string
* @return the replaced input string
* @throws IOException
*/
public String replaceVariable(String input) throws IOException {
return input.replace("$" + type.name(), getFile().getPath()); //$NON-NLS-1$
}
/**
* Add variable to environment map.
*
* @param env
* the environment where this element should be added
* @throws IOException
*/
public void addToEnv(Map<String, String> env) throws IOException {
env.put(type.name(), getFile().getPath());
}
private static File getTempFile(final File file, final String midName,
final File workingDir) throws IOException {
String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
// TODO: avoid long random file name (number generated by
// createTempFile)
return File.createTempFile(
fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
fileNameAndExtension[1], workingDir);
}
private static void copyFromStream(final File file,
final InputStream stream)
throws IOException, FileNotFoundException {
try (OutputStream outStream = new FileOutputStream(file)) {
int read = 0;
byte[] bytes = new byte[8 * 1024];
while ((read = stream.read(bytes)) != -1) {
outStream.write(bytes, 0, read);
}
} finally {
// stream can only be consumed once --> close it and invalidate
stream.close();
}
}
private static String[] splitBaseFileNameAndExtension(File file) {
String[] result = new String[2];
result[0] = file.getName();
result[1] = ""; //$NON-NLS-1$
int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
// if "." was found (>-1) and last-index is not first char (>0), then
// split (same behavior like cgit)
if (idx > 0) {
result[1] = result[0].substring(idx, result[0].length());
result[0] = result[0].substring(0, idx);
}
return result;
}
}