1 /* 2 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.nio.fs; 27 28 import java.nio.file.*; 29 import static java.nio.file.StandardOpenOption.*; 30 import java.nio.file.attribute.*; 31 import java.nio.channels.*; 32 import java.nio.ByteBuffer; 33 import java.io.*; 34 import java.util.*; 35 36 /** 37 * Base implementation class for a {@code Path}. 38 */ 39 40 abstract class AbstractPath extends Path { 41 protected AbstractPath() { } 42 43 @Override 44 public final Path createFile(FileAttribute<?>... attrs) 45 throws IOException 46 { 47 EnumSet<StandardOpenOption> options = EnumSet.of(CREATE_NEW, WRITE); 48 SeekableByteChannel sbc = newByteChannel(options, attrs); 49 try { 50 sbc.close(); 51 } catch (IOException x) { 52 // ignore 53 } 54 return this; 55 } 56 57 /** 58 * Deletes a file. The {@code failIfNotExists} parameters determines if an 59 * {@code IOException} is thrown when the file does not exist. 60 */ 61 abstract void implDelete(boolean failIfNotExists) throws IOException; 62 63 @Override 64 public final void delete() throws IOException { 65 implDelete(true); 66 } 67 68 @Override 69 public final void deleteIfExists() throws IOException { 70 implDelete(false); 71 } 72 73 @Override 74 public final InputStream newInputStream(OpenOption... options) 75 throws IOException 76 { 77 if (options.length > 0) { 78 for (OpenOption opt: options) { 79 if (opt != READ) 80 throw new UnsupportedOperationException("'" + opt + "' not allowed"); 81 } 82 } 83 return Channels.newInputStream(newByteChannel()); 84 } 85 86 @Override 87 public final OutputStream newOutputStream(OpenOption... options) 88 throws IOException 89 { 90 int len = options.length; 91 Set<OpenOption> opts = new HashSet<OpenOption>(len + 3); 92 if (len == 0) { 93 opts.add(CREATE); 94 opts.add(TRUNCATE_EXISTING); 95 } else { 96 for (OpenOption opt: options) { 97 if (opt == READ) 98 throw new IllegalArgumentException("READ not allowed"); 99 opts.add(opt); 100 } 101 } 102 opts.add(WRITE); 103 return Channels.newOutputStream(newByteChannel(opts)); 104 } 105 106 @Override 107 public final SeekableByteChannel newByteChannel(OpenOption... options) 108 throws IOException 109 { 110 Set<OpenOption> set = new HashSet<OpenOption>(options.length); 111 Collections.addAll(set, options); 112 return newByteChannel(set); 113 } 114 115 private static final DirectoryStream.Filter<Path> acceptAllFilter = 116 new DirectoryStream.Filter<Path>() { 117 @Override public boolean accept(Path entry) { return true; } 118 }; 119 120 @Override 121 public final DirectoryStream<Path> newDirectoryStream() throws IOException { 122 return newDirectoryStream(acceptAllFilter); 123 } 124 125 @Override 126 public final DirectoryStream<Path> newDirectoryStream(String glob) 127 throws IOException 128 { 129 // avoid creating a matcher if all entries are required. 130 if (glob.equals("*")) 131 return newDirectoryStream(); 132 133 // create a matcher and return a filter that uses it. 134 final PathMatcher matcher = getFileSystem().getPathMatcher("glob:" + glob); 135 DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() { 136 @Override 137 public boolean accept(Path entry) { 138 return matcher.matches(entry.getName()); 139 } 140 }; 141 return newDirectoryStream(filter); 142 } 143 144 @Override 145 public final boolean exists() { 146 try { 147 checkAccess(); 148 return true; 149 } catch (IOException x) { 150 // unable to determine if file exists 151 } 152 return false; 153 } 154 155 @Override 156 public final boolean notExists() { 157 try { 158 checkAccess(); 159 return false; 160 } catch (NoSuchFileException x) { 161 // file confirmed not to exist 162 return true; 163 } catch (IOException x) { 164 return false; 165 } 166 } 167 168 private static final WatchEvent.Modifier[] NO_MODIFIERS = new WatchEvent.Modifier[0]; 169 170 @Override 171 public final WatchKey register(WatchService watcher, 172 WatchEvent.Kind<?>... events) 173 throws IOException 174 { 175 return register(watcher, events, NO_MODIFIERS); 176 } 177 178 abstract void implCopyTo(Path target, CopyOption... options) 179 throws IOException; 180 181 @Override 182 public final Path copyTo(Path target, CopyOption... options) 183 throws IOException 184 { 185 if ((getFileSystem().provider() == target.getFileSystem().provider())) { 186 implCopyTo(target, options); 187 } else { 188 copyToForeignTarget(target, options); 189 } 190 return target; 191 } 192 193 abstract void implMoveTo(Path target, CopyOption... options) 194 throws IOException; 195 196 @Override 197 public final Path moveTo(Path target, CopyOption... options) 198 throws IOException 199 { 200 if ((getFileSystem().provider() == target.getFileSystem().provider())) { 201 implMoveTo(target, options); 202 } else { 203 // different providers so copy + delete 204 copyToForeignTarget(target, convertMoveToCopyOptions(options)); 205 delete(); 206 } 207 return target; 208 } 209 210 /** 211 * Converts the given array of options for moving a file to options suitable 212 * for copying the file when a move is implemented as copy + delete. 213 */ 214 private static CopyOption[] convertMoveToCopyOptions(CopyOption... options) 215 throws AtomicMoveNotSupportedException 216 { 217 int len = options.length; 218 CopyOption[] newOptions = new CopyOption[len+2]; 219 for (int i=0; i<len; i++) { 220 CopyOption option = options[i]; 221 if (option == StandardCopyOption.ATOMIC_MOVE) { 222 throw new AtomicMoveNotSupportedException(null, null, 223 "Atomic move between providers is not supported"); 224 } 225 newOptions[i] = option; 226 } 227 newOptions[len] = LinkOption.NOFOLLOW_LINKS; 228 newOptions[len+1] = StandardCopyOption.COPY_ATTRIBUTES; 229 return newOptions; 230 } 231 232 /** 233 * Parses the arguments for a file copy operation. 234 */ 235 private static class CopyOptions { 236 boolean replaceExisting = false; 237 boolean copyAttributes = false; 238 boolean followLinks = true; 239 240 private CopyOptions() { } 241 242 static CopyOptions parse(CopyOption... options) { 243 CopyOptions result = new CopyOptions(); 244 for (CopyOption option: options) { 245 if (option == StandardCopyOption.REPLACE_EXISTING) { 246 result.replaceExisting = true; 247 continue; 248 } 249 if (option == LinkOption.NOFOLLOW_LINKS) { 250 result.followLinks = false; 251 continue; 252 } 253 if (option == StandardCopyOption.COPY_ATTRIBUTES) { 254 result.copyAttributes = true; 255 continue; 256 } 257 if (option == null) 258 throw new NullPointerException(); 259 throw new UnsupportedOperationException("'" + option + 260 "' is not a recognized copy option"); 261 } 262 return result; 263 } 264 } 265 266 /** 267 * Simple cross-provider copy where the target is a Path. 268 */ 269 private void copyToForeignTarget(Path target, CopyOption... options) 270 throws IOException 271 { 272 CopyOptions opts = CopyOptions.parse(options); 273 LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] : 274 new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; 275 276 // attributes of source file 277 BasicFileAttributes attrs = Attributes 278 .readBasicFileAttributes(this, linkOptions); 279 if (attrs.isSymbolicLink()) 280 throw new IOException("Copying of symbolic links not supported"); 281 282 // check if target exists 283 boolean exists; 284 if (opts.replaceExisting) { 285 try { 286 target.deleteIfExists(); 287 exists = false; 288 } catch (DirectoryNotEmptyException x) { 289 // let exception translate to FileAlreadyExistsException (6895012) 290 exists = true; 291 } 292 } else { 293 exists = target.exists(); 294 } 295 if (exists) 296 throw new FileAlreadyExistsException(target.toString()); 297 298 // create directory or file 299 if (attrs.isDirectory()) { 300 target.createDirectory(); 301 } else { 302 copyRegularFileToForeignTarget(target); 303 } 304 305 // copy basic attributes to target 306 if (opts.copyAttributes) { 307 BasicFileAttributeView view = target 308 .getFileAttributeView(BasicFileAttributeView.class, linkOptions); 309 try { 310 view.setTimes(attrs.lastModifiedTime(), 311 attrs.lastAccessTime(), 312 attrs.creationTime()); 313 } catch (IOException x) { 314 // rollback 315 try { 316 target.delete(); 317 } catch (IOException ignore) { } 318 throw x; 319 } 320 } 321 } 322 323 324 /** 325 * Simple copy of regular file to a target file that exists. 326 */ 327 private void copyRegularFileToForeignTarget(Path target) 328 throws IOException 329 { 330 ReadableByteChannel rbc = newByteChannel(); 331 try { 332 // open target file for writing 333 SeekableByteChannel sbc = target.newByteChannel(CREATE_NEW, WRITE); 334 335 // simple copy loop 336 try { 337 ByteBuffer buf = ByteBuffer.wrap(new byte[8192]); 338 int n = 0; 339 for (;;) { 340 n = rbc.read(buf); 341 if (n < 0) 342 break; 343 assert n > 0; 344 buf.flip(); 345 while (buf.hasRemaining()) { 346 sbc.write(buf); 347 } 348 buf.rewind(); 349 } 350 351 } finally { 352 sbc.close(); 353 } 354 } finally { 355 rbc.close(); 356 } 357 } 358 359 /** 360 * Splits the given attribute name into the name of an attribute view and 361 * the attribute. If the attribute view is not identified then it assumed 362 * to be "basic". 363 */ 364 private static String[] split(String attribute) { 365 String[] s = new String[2]; 366 int pos = attribute.indexOf(':'); 367 if (pos == -1) { 368 s[0] = "basic"; 369 s[1] = attribute; 370 } else { 371 s[0] = attribute.substring(0, pos++); 372 s[1] = (pos == attribute.length()) ? "" : attribute.substring(pos); 373 } 374 return s; 375 } 376 377 /** 378 * Gets a DynamicFileAttributeView by name. Returns {@code null} if the 379 * view is not available. 380 */ 381 abstract DynamicFileAttributeView getFileAttributeView(String name, 382 LinkOption... options); 383 384 @Override 385 public final void setAttribute(String attribute, 386 Object value, 387 LinkOption... options) 388 throws IOException 389 { 390 String[] s = split(attribute); 391 DynamicFileAttributeView view = getFileAttributeView(s[0], options); 392 if (view == null) 393 throw new UnsupportedOperationException("View '" + s[0] + "' not available"); 394 view.setAttribute(s[1], value); 395 } 396 397 @Override 398 public final Object getAttribute(String attribute, LinkOption... options) 399 throws IOException 400 { 401 String[] s = split(attribute); 402 DynamicFileAttributeView view = getFileAttributeView(s[0], options); 403 return (view == null) ? null : view.getAttribute(s[1]); 404 } 405 406 @Override 407 public final Map<String,?> readAttributes(String attributes, LinkOption... options) 408 throws IOException 409 { 410 String[] s = split(attributes); 411 DynamicFileAttributeView view = getFileAttributeView(s[0], options); 412 if (view == null) 413 return Collections.emptyMap(); 414 return view.readAttributes(s[1].split(",")); 415 } 416 }