FileElement.java

  1. /*
  2.  * Copyright (C) 2018-2021, Andre Bossert <andre.bossert@siemens.com>
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.internal.diffmergetool;

  11. import java.io.File;
  12. import java.io.FileNotFoundException;
  13. import java.io.FileOutputStream;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.io.OutputStream;
  17. import java.util.Map;

  18. import org.eclipse.jgit.diff.DiffEntry;

  19. /**
  20.  * The element used as left or right file for compare.
  21.  *
  22.  */
  23. public class FileElement {

  24.     /**
  25.      * The file element type.
  26.      *
  27.      */
  28.     public enum Type {
  29.         /**
  30.          * The local file element (ours).
  31.          */
  32.         LOCAL,
  33.         /**
  34.          * The remote file element (theirs).
  35.          */
  36.         REMOTE,
  37.         /**
  38.          * The merged file element (path in worktree).
  39.          */
  40.         MERGED,
  41.         /**
  42.          * The base file element (of ours and theirs).
  43.          */
  44.         BASE,
  45.         /**
  46.          * The backup file element (copy of merged / conflicted).
  47.          */
  48.         BACKUP
  49.     }

  50.     private final String path;

  51.     private final Type type;

  52.     private final File workDir;

  53.     private InputStream stream;

  54.     private File tempFile;

  55.     /**
  56.      * Creates file element for path.
  57.      *
  58.      * @param path
  59.      *            the file path
  60.      * @param type
  61.      *            the element type
  62.      */
  63.     public FileElement(String path, Type type) {
  64.         this(path, type, null);
  65.     }

  66.     /**
  67.      * Creates file element for path.
  68.      *
  69.      * @param path
  70.      *            the file path
  71.      * @param type
  72.      *            the element type
  73.      * @param workDir
  74.      *            the working directory of the path (can be null, then current
  75.      *            working dir is used)
  76.      */
  77.     public FileElement(String path, Type type, File workDir) {
  78.         this(path, type, workDir, null);
  79.     }

  80.     /**
  81.      * @param path
  82.      *            the file path
  83.      * @param type
  84.      *            the element type
  85.      * @param workDir
  86.      *            the working directory of the path (can be null, then current
  87.      *            working dir is used)
  88.      * @param stream
  89.      *            the object stream to load and write on demand, @see getFile(),
  90.      *            to tempFile once (can be null)
  91.      */
  92.     public FileElement(String path, Type type, File workDir,
  93.             InputStream stream) {
  94.         this.path = path;
  95.         this.type = type;
  96.         this.workDir = workDir;
  97.         this.stream = stream;
  98.     }

  99.     /**
  100.      * @return the file path
  101.      */
  102.     public String getPath() {
  103.         return path;
  104.     }

  105.     /**
  106.      * @return the element type
  107.      */
  108.     public Type getType() {
  109.         return type;
  110.     }

  111.     /**
  112.      * Return
  113.      * <ul>
  114.      * <li>a temporary file if already created and stream is not valid</li>
  115.      * <li>OR a real file from work tree: if no temp file was created (@see
  116.      * createTempFile()) and if no stream was set</li>
  117.      * <li>OR an empty temporary file if path is "/dev/null"</li>
  118.      * <li>OR a temporary file with stream content if stream is valid (not
  119.      * null); stream is closed and invalidated (set to null) after write to temp
  120.      * file, so stream is used only once during first call!</li>
  121.      * </ul>
  122.      *
  123.      * @return the object stream
  124.      * @throws IOException
  125.      */
  126.     public File getFile() throws IOException {
  127.         // if we have already temp file and no stream
  128.         // then just return this temp file (it was filled from outside)
  129.         if ((tempFile != null) && (stream == null)) {
  130.             return tempFile;
  131.         }
  132.         File file = new File(workDir, path);
  133.         // if we have a stream or file is missing (path is "/dev/null")
  134.         // then optionally create temporary file and fill it with stream content
  135.         if ((stream != null) || isNullPath()) {
  136.             if (tempFile == null) {
  137.                 tempFile = getTempFile(file, type.name(), null);
  138.             }
  139.             if (stream != null) {
  140.                 copyFromStream(tempFile, stream);
  141.             }
  142.             // invalidate the stream, because it is used once
  143.             stream = null;
  144.             return tempFile;
  145.         }
  146.         return file;
  147.     }

  148.     /**
  149.      * Check if path id "/dev/null"
  150.      *
  151.      * @return true if path is "/dev/null"
  152.      */
  153.     public boolean isNullPath() {
  154.         return path.equals(DiffEntry.DEV_NULL);
  155.     }

  156.     /**
  157.      * Create temporary file in given or system temporary directory.
  158.      *
  159.      * @param directory
  160.      *            the directory for the file (can be null); if null system
  161.      *            temporary directory is used
  162.      * @return temporary file in directory or in the system temporary directory
  163.      * @throws IOException
  164.      */
  165.     public File createTempFile(File directory) throws IOException {
  166.         if (tempFile == null) {
  167.             tempFile = getTempFile(new File(path), type.name(), directory);
  168.         }
  169.         return tempFile;
  170.     }

  171.     /**
  172.      * Delete and invalidate temporary file if necessary.
  173.      */
  174.     public void cleanTemporaries() {
  175.         if (tempFile != null && tempFile.exists()) {
  176.             tempFile.delete();
  177.         }
  178.         tempFile = null;
  179.     }

  180.     /**
  181.      * Replace variable in input.
  182.      *
  183.      * @param input
  184.      *            the input string
  185.      * @return the replaced input string
  186.      * @throws IOException
  187.      */
  188.     public String replaceVariable(String input) throws IOException {
  189.         return input.replace("$" + type.name(), getFile().getPath()); //$NON-NLS-1$
  190.     }

  191.     /**
  192.      * Add variable to environment map.
  193.      *
  194.      * @param env
  195.      *            the environment where this element should be added
  196.      * @throws IOException
  197.      */
  198.     public void addToEnv(Map<String, String> env) throws IOException {
  199.         env.put(type.name(), getFile().getPath());
  200.     }

  201.     private static File getTempFile(final File file, final String midName,
  202.             final File workingDir) throws IOException {
  203.         String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
  204.         // TODO: avoid long random file name (number generated by
  205.         // createTempFile)
  206.         return File.createTempFile(
  207.                 fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
  208.                 fileNameAndExtension[1], workingDir);
  209.     }

  210.     private static void copyFromStream(final File file,
  211.             final InputStream stream)
  212.             throws IOException, FileNotFoundException {
  213.         try (OutputStream outStream = new FileOutputStream(file)) {
  214.             int read = 0;
  215.             byte[] bytes = new byte[8 * 1024];
  216.             while ((read = stream.read(bytes)) != -1) {
  217.                 outStream.write(bytes, 0, read);
  218.             }
  219.         } finally {
  220.             // stream can only be consumed once --> close it and invalidate
  221.             stream.close();
  222.         }
  223.     }

  224.     private static String[] splitBaseFileNameAndExtension(File file) {
  225.         String[] result = new String[2];
  226.         result[0] = file.getName();
  227.         result[1] = ""; //$NON-NLS-1$
  228.         int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
  229.         // if "." was found (>-1) and last-index is not first char (>0), then
  230.         // split (same behavior like cgit)
  231.         if (idx > 0) {
  232.             result[1] = result[0].substring(idx, result[0].length());
  233.             result[0] = result[0].substring(0, idx);
  234.         }
  235.         return result;
  236.     }

  237. }