View Javadoc
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  
11  package org.eclipse.jgit.internal.diffmergetool;
12  
13  import java.io.File;
14  import java.io.FileNotFoundException;
15  import java.io.FileOutputStream;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.util.Map;
20  
21  import org.eclipse.jgit.diff.DiffEntry;
22  
23  /**
24   * The element used as left or right file for compare.
25   *
26   */
27  public class FileElement {
28  
29  	/**
30  	 * The file element type.
31  	 *
32  	 */
33  	public enum Type {
34  		/**
35  		 * The local file element (ours).
36  		 */
37  		LOCAL,
38  		/**
39  		 * The remote file element (theirs).
40  		 */
41  		REMOTE,
42  		/**
43  		 * The merged file element (path in worktree).
44  		 */
45  		MERGED,
46  		/**
47  		 * The base file element (of ours and theirs).
48  		 */
49  		BASE,
50  		/**
51  		 * The backup file element (copy of merged / conflicted).
52  		 */
53  		BACKUP
54  	}
55  
56  	private final String path;
57  
58  	private final Type type;
59  
60  	private final File workDir;
61  
62  	private InputStream stream;
63  
64  	private File tempFile;
65  
66  	/**
67  	 * Creates file element for path.
68  	 *
69  	 * @param path
70  	 *            the file path
71  	 * @param type
72  	 *            the element type
73  	 */
74  	public FileElement(String path, Type type) {
75  		this(path, type, null);
76  	}
77  
78  	/**
79  	 * Creates file element for path.
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  	 */
89  	public FileElement(String path, Type type, File workDir) {
90  		this(path, type, workDir, null);
91  	}
92  
93  	/**
94  	 * @param path
95  	 *            the file path
96  	 * @param type
97  	 *            the element type
98  	 * @param workDir
99  	 *            the working directory of the path (can be null, then current
100 	 *            working dir is used)
101 	 * @param stream
102 	 *            the object stream to load and write on demand, @see getFile(),
103 	 *            to tempFile once (can be null)
104 	 */
105 	public FileElement(String path, Type type, File workDir,
106 			InputStream stream) {
107 		this.path = path;
108 		this.type = type;
109 		this.workDir = workDir;
110 		this.stream = stream;
111 	}
112 
113 	/**
114 	 * @return the file path
115 	 */
116 	public String getPath() {
117 		return path;
118 	}
119 
120 	/**
121 	 * @return the element type
122 	 */
123 	public Type getType() {
124 		return type;
125 	}
126 
127 	/**
128 	 * Return
129 	 * <ul>
130 	 * <li>a temporary file if already created and stream is not valid</li>
131 	 * <li>OR a real file from work tree: if no temp file was created (@see
132 	 * createTempFile()) and if no stream was set</li>
133 	 * <li>OR an empty temporary file if path is "/dev/null"</li>
134 	 * <li>OR a temporary file with stream content if stream is valid (not
135 	 * null); stream is closed and invalidated (set to null) after write to temp
136 	 * file, so stream is used only once during first call!</li>
137 	 * </ul>
138 	 *
139 	 * @return the object stream
140 	 * @throws IOException
141 	 */
142 	public File getFile() throws IOException {
143 		// if we have already temp file and no stream
144 		// then just return this temp file (it was filled from outside)
145 		if ((tempFile != null) && (stream == null)) {
146 			return tempFile;
147 		}
148 		File file = new File(workDir, path);
149 		// if we have a stream or file is missing (path is "/dev/null")
150 		// then optionally create temporary file and fill it with stream content
151 		if ((stream != null) || isNullPath()) {
152 			if (tempFile == null) {
153 				tempFile = getTempFile(file, type.name(), null);
154 			}
155 			if (stream != null) {
156 				copyFromStream(tempFile, stream);
157 			}
158 			// invalidate the stream, because it is used once
159 			stream = null;
160 			return tempFile;
161 		}
162 		return file;
163 	}
164 
165 	/**
166 	 * Check if path id "/dev/null"
167 	 *
168 	 * @return true if path is "/dev/null"
169 	 */
170 	public boolean isNullPath() {
171 		return path.equals(DiffEntry.DEV_NULL);
172 	}
173 
174 	/**
175 	 * Create temporary file in given or system temporary directory.
176 	 *
177 	 * @param directory
178 	 *            the directory for the file (can be null); if null system
179 	 *            temporary directory is used
180 	 * @return temporary file in directory or in the system temporary directory
181 	 * @throws IOException
182 	 */
183 	public File createTempFile(File directory) throws IOException {
184 		if (tempFile == null) {
185 			tempFile = getTempFile(new File(path), type.name(), directory);
186 		}
187 		return tempFile;
188 	}
189 
190 	/**
191 	 * Delete and invalidate temporary file if necessary.
192 	 */
193 	public void cleanTemporaries() {
194 		if (tempFile != null && tempFile.exists()) {
195 			tempFile.delete();
196 		}
197 		tempFile = null;
198 	}
199 
200 	/**
201 	 * Replace variable in input.
202 	 *
203 	 * @param input
204 	 *            the input string
205 	 * @return the replaced input string
206 	 * @throws IOException
207 	 */
208 	public String replaceVariable(String input) throws IOException {
209 		return input.replace("$" + type.name(), getFile().getPath()); //$NON-NLS-1$
210 	}
211 
212 	/**
213 	 * Add variable to environment map.
214 	 *
215 	 * @param env
216 	 *            the environment where this element should be added
217 	 * @throws IOException
218 	 */
219 	public void addToEnv(Map<String, String> env) throws IOException {
220 		env.put(type.name(), getFile().getPath());
221 	}
222 
223 	private static File getTempFile(final File file, final String midName,
224 			final File workingDir) throws IOException {
225 		String[] fileNameAndExtension = splitBaseFileNameAndExtension(file);
226 		// TODO: avoid long random file name (number generated by
227 		// createTempFile)
228 		return File.createTempFile(
229 				fileNameAndExtension[0] + "_" + midName + "_", //$NON-NLS-1$ //$NON-NLS-2$
230 				fileNameAndExtension[1], workingDir);
231 	}
232 
233 	private static void copyFromStream(final File file,
234 			final InputStream stream)
235 			throws IOException, FileNotFoundException {
236 		try (OutputStream outStream = new FileOutputStream(file)) {
237 			int read = 0;
238 			byte[] bytes = new byte[8 * 1024];
239 			while ((read = stream.read(bytes)) != -1) {
240 				outStream.write(bytes, 0, read);
241 			}
242 		} finally {
243 			// stream can only be consumed once --> close it and invalidate
244 			stream.close();
245 		}
246 	}
247 
248 	private static String[] splitBaseFileNameAndExtension(File file) {
249 		String[] result = new String[2];
250 		result[0] = file.getName();
251 		result[1] = ""; //$NON-NLS-1$
252 		int idx = result[0].lastIndexOf("."); //$NON-NLS-1$
253 		// if "." was found (>-1) and last-index is not first char (>0), then
254 		// split (same behavior like cgit)
255 		if (idx > 0) {
256 			result[1] = result[0].substring(idx, result[0].length());
257 			result[0] = result[0].substring(0, idx);
258 		}
259 		return result;
260 	}
261 
262 }