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 }