| /******************************************************************************* |
| * Copyright (c) 2005, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * James Blackburn (Broadcom Corp.) - ongoing development |
| *******************************************************************************/ |
| package org.eclipse.core.filesystem.provider; |
| |
| import java.io.*; |
| import java.net.URI; |
| import org.eclipse.core.filesystem.*; |
| import org.eclipse.core.internal.filesystem.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * The abstract superclass of all {@link IFileStore} implementations. All |
| * file stores must subclass this base class, implementing all abstract |
| * methods according to their specification in the {@link IFileStore} API. |
| * <p> |
| * Clients may subclass this class. |
| * </p> |
| * @since org.eclipse.core.filesystem 1.0 |
| */ |
| public abstract class FileStore extends PlatformObject implements IFileStore { |
| /** |
| * A file info array of size zero that can be used as a return value for methods |
| * that return IFileInfo[] to avoid creating garbage objects. |
| */ |
| protected static final IFileInfo[] EMPTY_FILE_INFO_ARRAY = {}; |
| |
| /** |
| * A string array of size zero that can be used as a return value for methods |
| * that return String[] to avoid creating garbage objects. |
| */ |
| protected static final String[] EMPTY_STRING_ARRAY = {}; |
| |
| /** |
| * Transfers the contents of an input stream to an output stream, using a large |
| * buffer. |
| * |
| * @param source The input stream to transfer |
| * @param destination The destination stream of the transfer |
| * @param length the size of the file or -1 if not known |
| * @param path A path representing the data being transferred for use in error |
| * messages. |
| * @param monitor A progress monitor |
| * @throws CoreException |
| */ |
| private static final void transferStreams(InputStream source, OutputStream destination, long length, String path, IProgressMonitor monitor) throws CoreException { |
| byte[] buffer = new byte[8192]; |
| SubMonitor subMonitor = SubMonitor.convert(monitor, length >= 0 ? 1 + (int) (length / buffer.length) : 1000); |
| try { |
| while (true) { |
| int bytesRead = -1; |
| try { |
| bytesRead = source.read(buffer); |
| } catch (IOException e) { |
| String msg = NLS.bind(Messages.failedReadDuringWrite, path); |
| Policy.error(EFS.ERROR_READ, msg, e); |
| } |
| try { |
| if (bytesRead == -1) { |
| destination.close(); |
| break; |
| } |
| destination.write(buffer, 0, bytesRead); |
| } catch (IOException e) { |
| String msg = NLS.bind(Messages.couldNotWrite, path); |
| Policy.error(EFS.ERROR_WRITE, msg, e); |
| } |
| subMonitor.worked(1); |
| } |
| } finally { |
| Policy.safeClose(source); |
| Policy.safeClose(destination); |
| } |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#childInfos(int, IProgressMonitor)}. |
| * Subclasses should override this method where a more efficient implementation |
| * is possible. This default implementation calls {@link #fetchInfo()} on each |
| * child, which will result in a file system call for each child. |
| */ |
| @Override |
| public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException { |
| IFileStore[] childStores = childStores(options, monitor); |
| IFileInfo[] childInfos = new IFileInfo[childStores.length]; |
| for (int i = 0; i < childStores.length; i++) { |
| childInfos[i] = childStores[i].fetchInfo(); |
| } |
| return childInfos; |
| } |
| |
| @Override |
| public abstract String[] childNames(int options, IProgressMonitor monitor) throws CoreException; |
| |
| /** |
| * The default implementation of {@link IFileStore#childStores(int, IProgressMonitor)}. |
| * Subclasses may override. |
| */ |
| @Override |
| public IFileStore[] childStores(int options, IProgressMonitor monitor) throws CoreException { |
| String[] children = childNames(options, monitor); |
| IFileStore[] wrapped = new IFileStore[children.length]; |
| for (int i = 0; i < wrapped.length; i++) |
| wrapped[i] = getChild(children[i]); |
| return wrapped; |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#copy(IFileStore, int, IProgressMonitor)}. |
| * This implementation performs a copy by using other primitive methods. |
| * Subclasses may override this method. |
| */ |
| @Override |
| public void copy(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { |
| final IFileInfo sourceInfo = fetchInfo(EFS.NONE, null); |
| if (sourceInfo.isDirectory()) { |
| copyDirectory(sourceInfo, destination, options, monitor); |
| } else { |
| copyFile(sourceInfo, destination, options, monitor); |
| } |
| } |
| |
| /** |
| * Recursively copies a directory as specified by |
| * {@link IFileStore#copy(IFileStore, int, IProgressMonitor)}. |
| * |
| * @param sourceInfo The current file information for the source of the move |
| * @param destination The destination of the copy. |
| * @param options bit-wise or of option flag constants ( |
| * {@link EFS#OVERWRITE} or {@link EFS#SHALLOW}). |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| * @exception CoreException if this method fails. Reasons include: |
| * <ul> |
| * <li> This store does not exist.</li> |
| * <li> A file of the same name already exists at the copy destination.</li> |
| * </ul> |
| */ |
| protected void copyDirectory(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { |
| IFileStore[] children = null; |
| int opWork = 1; |
| if ((options & EFS.SHALLOW) == 0) { |
| children = childStores(EFS.NONE, null); |
| opWork += children.length; |
| } |
| SubMonitor subMonitor = SubMonitor.convert(monitor, opWork); |
| subMonitor.subTask(NLS.bind(Messages.copying, toString())); |
| // create directory |
| destination.mkdir(EFS.NONE, subMonitor.newChild(1)); |
| // copy attributes |
| transferAttributes(sourceInfo, destination); |
| |
| if (children == null) |
| return; |
| // copy children |
| for (IFileStore c : children) { |
| c.copy(destination.getChild(c.getName()), options, subMonitor.newChild(1)); |
| } |
| } |
| |
| /** |
| * Copies a file as specified by |
| * {@link IFileStore#copy(IFileStore, int, IProgressMonitor)}. |
| |
| * @param sourceInfo The current file information for the source of the move |
| * @param destination The destination of the copy. |
| * @param options bit-wise or of option flag constants ( |
| * {@link EFS#OVERWRITE} or {@link EFS#SHALLOW}). |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| * @exception CoreException if this method fails. Reasons include: |
| * <ul> |
| * <li> This store does not exist.</li> |
| * <li> The <code>OVERWRITE</code> flag is not specified and a file of the |
| * same name already exists at the copy destination.</li> |
| * <li> A directory of the same name already exists at the copy destination.</li> |
| * </ul> |
| */ |
| protected void copyFile(IFileInfo sourceInfo, IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { |
| if ((options & EFS.OVERWRITE) == 0 && destination.fetchInfo().exists()) |
| Policy.error(EFS.ERROR_EXISTS, NLS.bind(Messages.fileExists, destination)); |
| long length = sourceInfo.getLength(); |
| String sourcePath = toString(); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, NLS.bind(Messages.copying, sourcePath), 100); |
| InputStream in = null; |
| OutputStream out = null; |
| try { |
| in = openInputStream(EFS.NONE, subMonitor.newChild(1)); |
| out = destination.openOutputStream(EFS.NONE, subMonitor.newChild(1)); |
| transferStreams(in, out, length, sourcePath, subMonitor.newChild(98)); |
| transferAttributes(sourceInfo, destination); |
| } catch (CoreException e) { |
| Policy.safeClose(in); |
| Policy.safeClose(out); |
| //if we failed to write, try to cleanup the half written file |
| if (!destination.fetchInfo(0, null).exists()) |
| destination.delete(EFS.NONE, null); |
| throw e; |
| } |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#delete(int, IProgressMonitor)}. |
| * This implementation always throws an exception indicating that deletion |
| * is not supported by this file system. This method should be overridden |
| * for all file systems on which deletion is supported. |
| * |
| * @param options bit-wise or of option flag constants |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| */ |
| @Override |
| public void delete(int options, IProgressMonitor monitor) throws CoreException { |
| Policy.error(EFS.ERROR_DELETE, NLS.bind(Messages.noImplDelete, toString())); |
| } |
| |
| /** |
| * This implementation of {@link Object#equals(Object)} defines |
| * equality based on the file store's URI. Subclasses should override |
| * this method to return <code>true</code> if and only if the two file stores |
| * represent the same resource in the backing file system. Issues to watch |
| * out for include whether the file system is case-sensitive, and whether trailing |
| * slashes are considered significant. Subclasses that override this method |
| * should also override {@link #hashCode()}. |
| * |
| * @param obj The object to compare with the receiver for equality |
| * @return <code>true</code> if this object is equal to the provided object, |
| * and <code>false</code> otherwise. |
| * @since org.eclipse.core.filesystem 1.1 |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (!(obj instanceof FileStore)) |
| return false; |
| return toURI().equals(((FileStore) obj).toURI()); |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#fetchInfo()}. |
| * This implementation forwards to {@link IFileStore#fetchInfo(int, IProgressMonitor)}. |
| * Subclasses may override this method. |
| */ |
| @Override |
| public IFileInfo fetchInfo() { |
| try { |
| return fetchInfo(EFS.NONE, null); |
| } catch (CoreException e) { |
| //there was an error contacting the file system, so treat it as non-existent file |
| FileInfo result = new FileInfo(getName()); |
| result.setExists(false); |
| return result; |
| } |
| } |
| |
| @Override |
| public abstract IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException; |
| |
| /** |
| * @deprecated use {@link #getFileStore(IPath)} instead |
| */ |
| @Deprecated |
| @Override |
| public IFileStore getChild(IPath path) { |
| IFileStore result = this; |
| for (int i = 0, imax = path.segmentCount(); i < imax; i++) |
| result = result.getChild(path.segment(i)); |
| return result; |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#getFileStore(IPath)} |
| * Subclasses may override. |
| * |
| * @since org.eclipse.core.filesystem 1.2 |
| */ |
| @Override |
| public IFileStore getFileStore(IPath path) { |
| IFileStore result = this; |
| String segment = null; |
| for (int i = 0, imax = path.segmentCount(); i < imax; i++) { |
| segment = path.segment(i); |
| if (segment.equals(".")) //$NON-NLS-1$ |
| continue; |
| else if (segment.equals("..") && result.getParent() != null) //$NON-NLS-1$ |
| result = result.getParent(); |
| else |
| result = result.getChild(segment); |
| } |
| return result; |
| } |
| |
| @Override |
| public abstract IFileStore getChild(String name); |
| |
| /** |
| * The default implementation of {@link IFileStore#getFileSystem()}. |
| * Subclasses may override. |
| */ |
| @Override |
| public IFileSystem getFileSystem() { |
| try { |
| return EFS.getFileSystem(toURI().getScheme()); |
| } catch (CoreException e) { |
| //this will only happen if toURI() has been incorrectly implemented |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Override |
| public abstract String getName(); |
| |
| @Override |
| public abstract IFileStore getParent(); |
| |
| /** |
| * This implementation of {@link Object#hashCode()} uses a definition |
| * of equality based on equality of the file store's URI. Subclasses that |
| * override {@link #equals(Object)} should also override this method |
| * to ensure the contract of {@link Object#hashCode()} is honored. |
| * |
| * @return A hash code value for this file store |
| * @since org.eclipse.core.filesystem 1.1 |
| */ |
| @Override |
| public int hashCode() { |
| return toURI().hashCode(); |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#isParentOf(IFileStore)}. |
| * This implementation performs parent calculation using other primitive methods. |
| * Subclasses may override this method. |
| * |
| * @param other The store to test for parentage. |
| * @return <code>true</code> if this store is a parent of the provided |
| * store, and <code>false</code> otherwise. |
| */ |
| @Override |
| public boolean isParentOf(IFileStore other) { |
| while (true) { |
| other = other.getParent(); |
| if (other == null) |
| return false; |
| if (this.equals(other)) |
| return true; |
| } |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#mkdir(int, IProgressMonitor)}. |
| * This implementation always throws an exception indicating that this file system |
| * is read only. This method should be overridden for all writable file systems. |
| * |
| * @param options bit-wise or of option flag constants |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| */ |
| @Override |
| public IFileStore mkdir(int options, IProgressMonitor monitor) throws CoreException { |
| Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.noImplWrite, toString())); |
| return null;//can't get here |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#move(IFileStore, int, IProgressMonitor)}. |
| * This implementation performs a move by using other primitive methods. |
| * Subclasses may override this method. |
| */ |
| @Override |
| public void move(IFileStore destination, int options, IProgressMonitor monitor) throws CoreException { |
| try { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, NLS.bind(Messages.moving, destination.toString()), 100); |
| copy(destination, options & EFS.OVERWRITE, subMonitor.newChild(70)); |
| delete(EFS.NONE, subMonitor.newChild(30)); |
| } catch (CoreException e) { |
| //throw new error to indicate failure occurred during a move |
| String message = NLS.bind(Messages.couldNotMove, toString()); |
| Policy.error(EFS.ERROR_WRITE, message, e); |
| } |
| } |
| |
| @Override |
| public abstract InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException; |
| |
| /** |
| * The default implementation of {@link IFileStore#openOutputStream(int, IProgressMonitor)}. |
| * This implementation always throws an exception indicating that this file system |
| * is read only. This method should be overridden for all writable file systems. |
| * <p> |
| * Implementations of this method are responsible for ensuring that the exact sequence |
| * of bytes written to the output stream are returned on a subsequent call to |
| * {@link #openInputStream(int, IProgressMonitor)}, unless there have been |
| * intervening modifications to the file in the file system. For example, the implementation |
| * of this method must not perform conversion of line terminator characters on text |
| * data in the stream. |
| * |
| * @param options bit-wise or of option flag constants |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| */ |
| @Override |
| public OutputStream openOutputStream(int options, IProgressMonitor monitor) throws CoreException { |
| Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.noImplWrite, toString())); |
| return null;//can't get here |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#putInfo(IFileInfo, int, IProgressMonitor)}. |
| * This implementation always throws an exception indicating that this file system |
| * is read only. This method should be overridden for all writable file systems. |
| * |
| * @param options bit-wise or of option flag constants |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| */ |
| @Override |
| public void putInfo(IFileInfo info, int options, IProgressMonitor monitor) throws CoreException { |
| Policy.error(EFS.ERROR_WRITE, NLS.bind(Messages.noImplWrite, toString())); |
| } |
| |
| /** |
| * The default implementation of {@link IFileStore#toLocalFile(int, IProgressMonitor)}. |
| * When the {@link EFS#CACHE} option is specified, this method returns |
| * a cached copy of this store in the local file system, or <code>null</code> if |
| * this store does not exist. |
| */ |
| @Override |
| public java.io.File toLocalFile(int options, IProgressMonitor monitor) throws CoreException { |
| //caching is the only recognized option |
| if (options != EFS.CACHE) |
| return null; |
| return FileCache.getCache().cache(this, monitor); |
| } |
| |
| /** |
| * Default implementation of {@link IFileStore#toString()}. This default implementation |
| * returns a string equal to the one returned by #toURI().toString(). Subclasses |
| * may override to provide a more specific string representation of this store. |
| * |
| * @return A string representation of this store. |
| */ |
| @Override |
| public String toString() { |
| return toURI().toString(); |
| } |
| |
| @Override |
| public abstract URI toURI(); |
| |
| private void transferAttributes(IFileInfo sourceInfo, IFileStore destination) throws CoreException { |
| int options = EFS.SET_ATTRIBUTES | EFS.SET_LAST_MODIFIED; |
| destination.putInfo(sourceInfo, options, null); |
| } |
| } |