/******************************************************************************* * Copyright (c) 2005, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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 (int i = 0; i < children.length; i++) { children[i].copy(destination.getChild(children[i].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); } }