1 /*******************************************************************************
2  * Copyright (c) 2005, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     James Blackburn (Broadcom Corp.) - ongoing development
14  *******************************************************************************/
15 package org.eclipse.core.filesystem.provider;
16 
17 import java.io.*;
18 import java.net.URI;
19 import org.eclipse.core.filesystem.*;
20 import org.eclipse.core.internal.filesystem.*;
21 import org.eclipse.core.runtime.*;
22 import org.eclipse.osgi.util.NLS;
23 
24 /**
25  * The abstract superclass of all {@link IFileStore} implementations.  All
26  * file stores must subclass this base class, implementing all abstract
27  * methods according to their specification in the {@link IFileStore} API.
28  * <p>
29  * Clients may subclass this class.
30  * </p>
31  * @since org.eclipse.core.filesystem 1.0
32  */
33 public abstract class FileStore extends PlatformObject implements IFileStore {
34 	/**
35 	 * A file info array of size zero that can be used as a return value for methods
36 	 * that return IFileInfo[] to avoid creating garbage objects.
37 	 */
38 	protected static final IFileInfo[] EMPTY_FILE_INFO_ARRAY = {};
39 
40 	/**
41 	 * A string array of size zero that can be used as a return value for methods
42 	 * that return String[] to avoid creating garbage objects.
43 	 */
44 	protected static final String[] EMPTY_STRING_ARRAY = {};
45 
46 	/**
47 	 * Transfers the contents of an input stream to an output stream, using a large
48 	 * buffer.
49 	 *
50 	 * @param source The input stream to transfer
51 	 * @param destination The destination stream of the transfer
52 	 * @param length the size of the file or -1 if not known
53 	 * @param path A path representing the data being transferred for use in error
54 	 * messages.
55 	 * @param monitor A progress monitor
56 	 * @throws CoreException
57 	 */
transferStreams(InputStream source, OutputStream destination, long length, String path, IProgressMonitor monitor)58 	private static final void transferStreams(InputStream source, OutputStream destination, long length, String path, IProgressMonitor monitor) throws CoreException {
59 		byte[] buffer = new byte[8192];
60 		SubMonitor subMonitor = SubMonitor.convert(monitor, length >= 0 ? 1 + (int) (length / buffer.length) : 1000);
61 		try {
62 			while (true) {
63 				int bytesRead = -1;
64 				try {
65 					bytesRead = source.read(buffer);
66 				} catch (IOException e) {
67 					String msg = NLS.bind(Messages.failedReadDuringWrite, path);
68 					Policy.error(EFS.ERROR_READ, msg, e);
69 				}
70 				try {
71 					if (bytesRead == -1) {
72 						destination.close();
73 						break;
74 					}
75 					destination.write(buffer, 0, bytesRead);
76 				} catch (IOException e) {
77 					String msg = NLS.bind(Messages.couldNotWrite, path);
78 					Policy.error(EFS.ERROR_WRITE, msg, e);
79 				}
80 				subMonitor.worked(1);
81 			}
82 		} finally {
83 			Policy.safeClose(source);
84 			Policy.safeClose(destination);
85 		}
86 	}
87 
88 	/**
89 	 * The default implementation of {@link IFileStore#childInfos(int, IProgressMonitor)}.
90 	 * Subclasses should override this method where a more efficient implementation
91 	 * is possible.  This default implementation calls {@link #fetchInfo()} on each
92 	 * child, which will result in a file system call for each child.
93 	 */
94 	@Override
childInfos(int options, IProgressMonitor monitor)95 	public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException {
96 		IFileStore[] childStores = childStores(options, monitor);
97 		IFileInfo[] childInfos = new IFileInfo[childStores.length];
98 		for (int i = 0; i < childStores.length; i++) {
99 			childInfos[i] = childStores[i].fetchInfo();
100 		}
101 		return childInfos;
102 	}
103 
104 	@Override
childNames(int options, IProgressMonitor monitor)105 	public abstract String[] childNames(int options, IProgressMonitor monitor) throws CoreException;
106 
107 	/**
108 	 * The default implementation of {@link IFileStore#childStores(int, IProgressMonitor)}.
109 	 * Subclasses may override.
110 	 */
111 	@Override
childStores(int options, IProgressMonitor monitor)112 	public IFileStore[] childStores(int options, IProgressMonitor monitor) throws CoreException {
113 		String[] children = childNames(options, monitor);
114 		IFileStore[] wrapped = new IFileStore[children.length];
115 		for (int i = 0; i < wrapped.length; i++)
116 			wrapped[i] = getChild(children[i]);
117 		return wrapped;
118 	}
119 
120 	/**
121 	 * The default implementation of {@link IFileStore#copy(IFileStore, int, IProgressMonitor)}.
122 	 * This implementation performs a copy by using other primitive methods.
123 	 * Subclasses may override this method.
124 	 */
125 	@Override
copy(IFileStore destination, int options, IProgressMonitor monitor)126 	public void copy(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException {
127 		final IFileInfo sourceInfo = fetchInfo(EFS.NONE, null);
128 		if (sourceInfo.isDirectory()) {
129 			copyDirectory(sourceInfo, destination, options, monitor);
130 		} else {
131 			copyFile(sourceInfo, destination, options, monitor);
132 		}
133 	}
134 
135 	/**
136 	 * Recursively copies a directory as specified by
137 	 * {@link IFileStore#copy(IFileStore, int, IProgressMonitor)}.
138 	 *
139 	 * @param sourceInfo The current file information for the source of the move
140 	 * @param destination The destination of the copy.
141 	 * @param options bit-wise or of option flag constants (
142 	 * {@link EFS#OVERWRITE} or {@link EFS#SHALLOW}).
143 	 * @param monitor a progress monitor, or <code>null</code> if progress
144 	 *    reporting and cancellation are not desired
145 	 * @exception CoreException if this method fails. Reasons include:
146 	 * <ul>
147 	 * <li> This store does not exist.</li>
148 	 * <li> A file of the same name already exists at the copy destination.</li>
149 	 * </ul>
150 	 */
copyDirectory(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor)151 	protected void copyDirectory(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException {
152 		IFileStore[] children = null;
153 		int opWork = 1;
154 		if ((options & EFS.SHALLOW) == 0) {
155 			children = childStores(EFS.NONE, null);
156 			opWork += children.length;
157 		}
158 		SubMonitor subMonitor = SubMonitor.convert(monitor, opWork);
159 		subMonitor.subTask(NLS.bind(Messages.copying, toString()));
160 		// create directory
161 		destination.mkdir(EFS.NONE, subMonitor.newChild(1));
162 		// copy attributes
163 		transferAttributes(sourceInfo, destination);
164 
165 		if (children == null)
166 			return;
167 		// copy children
168 		for (IFileStore c : children) {
169 			c.copy(destination.getChild(c.getName()), options, subMonitor.newChild(1));
170 		}
171 	}
172 
173 	/**
174 	 * Copies a file as specified by
175 	 * {@link IFileStore#copy(IFileStore, int, IProgressMonitor)}.
176 
177 	 * @param sourceInfo The current file information for the source of the move
178 	 * @param destination The destination of the copy.
179 	 * @param options bit-wise or of option flag constants (
180 	 * {@link EFS#OVERWRITE} or {@link EFS#SHALLOW}).
181 	 * @param monitor a progress monitor, or <code>null</code> if progress
182 	 *    reporting and cancellation are not desired
183 	 * @exception CoreException if this method fails. Reasons include:
184 	 * <ul>
185 	 * <li> This store does not exist.</li>
186 	 * <li> The <code>OVERWRITE</code> flag is not specified and a file of the
187 	 * same name already exists at the copy destination.</li>
188 	 * <li> A directory of the same name already exists at the copy destination.</li>
189 	 * </ul>
190 	 */
copyFile(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor)191 	protected void copyFile(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException {
192 		if ((options & EFS.OVERWRITE) == 0 && destination.fetchInfo().exists())
193 			Policy.error(EFS.ERROR_EXISTS, NLS.bind(Messages.fileExists, destination));
194 		long length = sourceInfo.getLength();
195 		String sourcePath = toString();
196 		SubMonitor subMonitor = SubMonitor.convert(monitor, NLS.bind(Messages.copying, sourcePath), 100);
197 		InputStream in = null;
198 		OutputStream out = null;
199 		try {
200 			in = openInputStream(EFS.NONE, subMonitor.newChild(1));
201 			out = destination.openOutputStream(EFS.NONE, subMonitor.newChild(1));
202 			transferStreams(in, out, length, sourcePath, subMonitor.newChild(98));
203 			transferAttributes(sourceInfo, destination);
204 		} catch (CoreException e) {
205 			Policy.safeClose(in);
206 			Policy.safeClose(out);
207 			//if we failed to write, try to cleanup the half written file
208 			if (!destination.fetchInfo(0, null).exists())
209 				destination.delete(EFS.NONE, null);
210 			throw e;
211 		}
212 	}
213 
214 	/**
215 	 * The default implementation of {@link IFileStore#delete(int, IProgressMonitor)}.
216 	 * This implementation always throws an exception indicating that deletion
217 	 * is not supported by this file system.  This method should be overridden
218 	 * for all file systems on which deletion is supported.
219 	 *
220 	 * @param options bit-wise or of option flag constants
221 	 * @param monitor a progress monitor, or <code>null</code> if progress
222 	 *    reporting and cancellation are not desired
223 	 */
224 	@Override
delete(int options, IProgressMonitor monitor)225 	public void delete(int options, IProgressMonitor monitor) throws CoreException {
226 		Policy.error(EFS.ERROR_DELETE, NLS.bind(Messages.noImplDelete, toString()));
227 	}
228 
229 	/**
230 	 * This implementation of {@link Object#equals(Object)} defines
231 	 * equality based on the file store's URI.  Subclasses should override
232 	 * this method to return <code>true</code> if and only if the two file stores
233 	 * represent the same resource in the backing file system.  Issues to watch
234 	 * out for include whether the file system is case-sensitive, and whether trailing
235 	 * slashes are considered significant. Subclasses that override this method
236 	 * should also override {@link #hashCode()}.
237 	 *
238 	 * @param obj The object to compare with the receiver for equality
239 	 * @return <code>true</code> if this object is equal to the provided object,
240 	 * and <code>false</code> otherwise.
241 	 * @since org.eclipse.core.filesystem 1.1
242 	 */
243 	@Override
equals(Object obj)244 	public boolean equals(Object obj) {
245 		if (this == obj)
246 			return true;
247 		if (!(obj instanceof FileStore))
248 			return false;
249 		return toURI().equals(((FileStore) obj).toURI());
250 	}
251 
252 	/**
253 	 * The default implementation of {@link IFileStore#fetchInfo()}.
254 	 * This implementation forwards to {@link IFileStore#fetchInfo(int, IProgressMonitor)}.
255 	 * Subclasses may override this method.
256 	 */
257 	@Override
fetchInfo()258 	public IFileInfo fetchInfo() {
259 		try {
260 			return fetchInfo(EFS.NONE, null);
261 		} catch (CoreException e) {
262 			//there was an error contacting the file system, so treat it as non-existent file
263 			FileInfo result = new FileInfo(getName());
264 			result.setExists(false);
265 			return result;
266 		}
267 	}
268 
269 	@Override
fetchInfo(int options, IProgressMonitor monitor)270 	public abstract IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException;
271 
272 	/**
273 	 * @deprecated use {@link #getFileStore(IPath)} instead
274 	 */
275 	@Deprecated
276 	@Override
getChild(IPath path)277 	public IFileStore getChild(IPath path) {
278 		IFileStore result = this;
279 		for (int i = 0, imax = path.segmentCount(); i < imax; i++)
280 			result = result.getChild(path.segment(i));
281 		return result;
282 	}
283 
284 	/**
285 	 * The default implementation of {@link IFileStore#getFileStore(IPath)}
286 	 * Subclasses may override.
287 	 *
288 	 * @since org.eclipse.core.filesystem 1.2
289 	 */
290 	@Override
getFileStore(IPath path)291 	public IFileStore getFileStore(IPath path) {
292 		IFileStore result = this;
293 		String segment = null;
294 		for (int i = 0, imax = path.segmentCount(); i < imax; i++) {
295 			segment = path.segment(i);
296 			if (segment.equals(".")) //$NON-NLS-1$
297 				continue;
298 			else if (segment.equals("..") && result.getParent() != null) //$NON-NLS-1$
299 				result = result.getParent();
300 			else
301 				result = result.getChild(segment);
302 		}
303 		return result;
304 	}
305 
306 	@Override
getChild(String name)307 	public abstract IFileStore getChild(String name);
308 
309 	/**
310 	 * The default implementation of {@link IFileStore#getFileSystem()}.
311 	 * Subclasses may override.
312 	 */
313 	@Override
getFileSystem()314 	public IFileSystem getFileSystem() {
315 		try {
316 			return EFS.getFileSystem(toURI().getScheme());
317 		} catch (CoreException e) {
318 			//this will only happen if toURI() has been incorrectly implemented
319 			throw new RuntimeException(e);
320 		}
321 	}
322 
323 	@Override
getName()324 	public abstract String getName();
325 
326 	@Override
getParent()327 	public abstract IFileStore getParent();
328 
329 	/**
330 	 * This implementation of {@link Object#hashCode()} uses a definition
331 	 * of equality based on equality of the file store's URI.  Subclasses that
332 	 * override {@link #equals(Object)} should also override this method
333 	 * to ensure the contract of {@link Object#hashCode()} is honored.
334 	 *
335 	 * @return A hash code value for this file store
336 	 * @since org.eclipse.core.filesystem 1.1
337 	 */
338 	@Override
hashCode()339 	public int hashCode() {
340 		return toURI().hashCode();
341 	}
342 
343 	/**
344 	 * The default implementation of {@link IFileStore#isParentOf(IFileStore)}.
345 	 * This implementation performs parent calculation using other primitive methods.
346 	 * Subclasses may override this method.
347 	 *
348 	 * @param other The store to test for parentage.
349 	 * @return <code>true</code> if this store is a parent of the provided
350 	 * store, and <code>false</code> otherwise.
351 	 */
352 	@Override
isParentOf(IFileStore other)353 	public boolean isParentOf(IFileStore other) {
354 		while (true) {
355 			other = other.getParent();
356 			if (other == null)
357 				return false;
358 			if (this.equals(other))
359 				return true;
360 		}
361 	}
362 
363 	/**
364 	 * The default implementation of {@link IFileStore#mkdir(int, IProgressMonitor)}.
365 	 * This implementation always throws an exception indicating that this file system
366 	 * is read only. This method should be overridden for all writable file systems.
367 	 *
368 	 * @param options bit-wise or of option flag constants
369 	 * @param monitor a progress monitor, or <code>null</code> if progress
370 	 *    reporting and cancellation are not desired
371 	 */
372 	@Override
mkdir(int options, IProgressMonitor monitor)373 	public IFileStore mkdir(int options, IProgressMonitor monitor) throws CoreException {
374 		Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.noImplWrite, toString()));
375 		return null;//can't get here
376 	}
377 
378 	/**
379 	 * The default implementation of {@link IFileStore#move(IFileStore, int, IProgressMonitor)}.
380 	 * This implementation performs a move by using other primitive methods.
381 	 * Subclasses may override this method.
382 	 */
383 	@Override
move(IFileStore destination, int options, IProgressMonitor monitor)384 	public void move(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException {
385 		try {
386 			SubMonitor subMonitor = SubMonitor.convert(monitor, NLS.bind(Messages.moving, destination.toString()), 100);
387 			copy(destination, options & EFS.OVERWRITE, subMonitor.newChild(70));
388 			delete(EFS.NONE, subMonitor.newChild(30));
389 		} catch (CoreException e) {
390 			//throw new error to indicate failure occurred during a move
391 			String message = NLS.bind(Messages.couldNotMove, toString());
392 			Policy.error(EFS.ERROR_WRITE, message, e);
393 		}
394 	}
395 
396 	@Override
openInputStream(int options, IProgressMonitor monitor)397 	public abstract InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException;
398 
399 	/**
400 	 * The default implementation of {@link IFileStore#openOutputStream(int, IProgressMonitor)}.
401 	 * This implementation always throws an exception indicating that this file system
402 	 * is read only. This method should be overridden for all writable file systems.
403 	 * <p>
404 	 * Implementations of this method are responsible for ensuring that the exact sequence
405 	 * of bytes written to the output stream are returned on a subsequent call to
406 	 * {@link #openInputStream(int, IProgressMonitor)}, unless there have been
407 	 * intervening modifications to the file in the file system. For example, the implementation
408 	 * of this method must not perform conversion of line terminator characters on text
409 	 * data in the stream.
410 	 *
411 	 * @param options bit-wise or of option flag constants
412 	 * @param monitor a progress monitor, or <code>null</code> if progress
413 	 *    reporting and cancellation are not desired
414 	 */
415 	@Override
openOutputStream(int options, IProgressMonitor monitor)416 	public OutputStream openOutputStream(int options, IProgressMonitor monitor) throws CoreException {
417 		Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.noImplWrite, toString()));
418 		return null;//can't get here
419 	}
420 
421 	/**
422 	 * The default implementation of {@link IFileStore#putInfo(IFileInfo, int, IProgressMonitor)}.
423 	 * This implementation always throws an exception indicating that this file system
424 	 * is read only. This method should be overridden for all writable file systems.
425 	 *
426 	 * @param options bit-wise or of option flag constants
427 	 * @param monitor a progress monitor, or <code>null</code> if progress
428 	 *    reporting and cancellation are not desired
429 	 */
430 	@Override
putInfo(IFileInfo info, int options, IProgressMonitor monitor)431 	public void putInfo(IFileInfo info, int options, IProgressMonitor monitor) throws CoreException {
432 		Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.noImplWrite, toString()));
433 	}
434 
435 	/**
436 	 * The default implementation of {@link IFileStore#toLocalFile(int, IProgressMonitor)}.
437 	 * When the {@link EFS#CACHE} option is specified, this method returns
438 	 * a cached copy of this store in the local file system, or <code>null</code> if
439 	 * this store does not exist.
440 	 */
441 	@Override
toLocalFile(int options, IProgressMonitor monitor)442 	public java.io.File toLocalFile(int options, IProgressMonitor monitor) throws CoreException {
443 		//caching is the only recognized option
444 		if (options != EFS.CACHE)
445 			return null;
446 		return FileCache.getCache().cache(this, monitor);
447 	}
448 
449 	/**
450 	 * Default implementation of {@link IFileStore#toString()}. This default implementation
451 	 * returns a string equal to the one returned by #toURI().toString(). Subclasses
452 	 * may override to provide a more specific string representation of this store.
453 	 *
454 	 * @return A string representation of this store.
455 	 */
456 	@Override
toString()457 	public String toString() {
458 		return toURI().toString();
459 	}
460 
461 	@Override
toURI()462 	public abstract URI toURI();
463 
transferAttributes(IFileInfo sourceInfo, IFileStore destination)464 	private void transferAttributes(IFileInfo sourceInfo, IFileStore destination) throws CoreException {
465 		int options = EFS.SET_ATTRIBUTES | EFS.SET_LAST_MODIFIED;
466 		destination.putInfo(sourceInfo, options, null);
467 	}
468 }