|
1 /* |
|
2 * Copyright 2007-2009 Sun Microsystems, Inc. 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. Sun designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 * have any questions. |
|
24 */ |
|
25 |
|
26 package java.nio.file.spi; |
|
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 * <p> This class is intended to be extended by provider implementors. It |
|
40 * implements, or provides default implementations for several of the methods |
|
41 * defined by the {@code Path} class. It implements the {@link #copyTo copyTo} |
|
42 * and {@link #moveTo moveTo} methods for the case that the source and target |
|
43 * are not associated with the same provider. |
|
44 * |
|
45 * @since 1.7 |
|
46 */ |
|
47 |
|
48 public abstract class AbstractPath extends Path { |
|
49 |
|
50 /** |
|
51 * Initializes a new instance of this class. |
|
52 */ |
|
53 protected AbstractPath() { } |
|
54 |
|
55 /** |
|
56 * Deletes the file referenced by this object. |
|
57 * |
|
58 * <p> This method invokes the {@link #delete(boolean) delete(boolean)} |
|
59 * method with a parameter of {@code true}. It may be overridden where |
|
60 * required. |
|
61 * |
|
62 * @throws NoSuchFileException {@inheritDoc} |
|
63 * @throws DirectoryNotEmptyException {@inheritDoc} |
|
64 * @throws IOException {@inheritDoc} |
|
65 * @throws SecurityException {@inheritDoc} |
|
66 */ |
|
67 @Override |
|
68 public void delete() throws IOException { |
|
69 delete(true); |
|
70 } |
|
71 |
|
72 /** |
|
73 * Creates a new and empty file, failing if the file already exists. |
|
74 * |
|
75 * <p> This method invokes the {@link #newByteChannel(Set,FileAttribute[]) |
|
76 * newByteChannel(Set,FileAttribute...)} method to create the file. It may be |
|
77 * overridden where required. |
|
78 * |
|
79 * @throws IllegalArgumentException {@inheritDoc} |
|
80 * @throws FileAlreadyExistsException {@inheritDoc} |
|
81 * @throws IOException {@inheritDoc} |
|
82 * @throws SecurityException {@inheritDoc} |
|
83 */ |
|
84 @Override |
|
85 public Path createFile(FileAttribute<?>... attrs) |
|
86 throws IOException |
|
87 { |
|
88 EnumSet<StandardOpenOption> options = EnumSet.of(CREATE_NEW, WRITE); |
|
89 SeekableByteChannel sbc = newByteChannel(options, attrs); |
|
90 try { |
|
91 sbc.close(); |
|
92 } catch (IOException x) { |
|
93 // ignore |
|
94 } |
|
95 return this; |
|
96 } |
|
97 |
|
98 /** |
|
99 * Opens or creates a file, returning a seekable byte channel to access the |
|
100 * file. |
|
101 * |
|
102 * <p> This method invokes the {@link #newByteChannel(Set,FileAttribute[]) |
|
103 * newByteChannel(Set,FileAttribute...)} method to open or create the file. |
|
104 * It may be overridden where required. |
|
105 * |
|
106 * @throws IllegalArgumentException {@inheritDoc} |
|
107 * @throws FileAlreadyExistsException {@inheritDoc} |
|
108 * @throws IOException {@inheritDoc} |
|
109 * @throws SecurityException {@inheritDoc} |
|
110 */ |
|
111 @Override |
|
112 public SeekableByteChannel newByteChannel(OpenOption... options) |
|
113 throws IOException |
|
114 { |
|
115 Set<OpenOption> set = new HashSet<OpenOption>(options.length); |
|
116 Collections.addAll(set, options); |
|
117 return newByteChannel(set); |
|
118 } |
|
119 |
|
120 /** |
|
121 * Opens the file located by this path for reading, returning an input |
|
122 * stream to read bytes from the file. |
|
123 * |
|
124 * <p> This method returns an {@code InputStream} that is constructed by |
|
125 * invoking the {@link java.nio.channels.Channels#newInputStream |
|
126 * Channels.newInputStream} method. It may be overridden where a more |
|
127 * efficient implementation is available. |
|
128 * |
|
129 * @throws IOException {@inheritDoc} |
|
130 * @throws SecurityException {@inheritDoc} |
|
131 */ |
|
132 @Override |
|
133 public InputStream newInputStream() throws IOException { |
|
134 return Channels.newInputStream(newByteChannel()); |
|
135 } |
|
136 |
|
137 // opts must be modifiable |
|
138 private OutputStream implNewOutputStream(Set<OpenOption> opts, |
|
139 FileAttribute<?>... attrs) |
|
140 throws IOException |
|
141 { |
|
142 if (opts.isEmpty()) { |
|
143 opts.add(CREATE); |
|
144 opts.add(TRUNCATE_EXISTING); |
|
145 } else { |
|
146 if (opts.contains(READ)) |
|
147 throw new IllegalArgumentException("READ not allowed"); |
|
148 } |
|
149 opts.add(WRITE); |
|
150 return Channels.newOutputStream(newByteChannel(opts, attrs)); |
|
151 } |
|
152 |
|
153 /** |
|
154 * Opens or creates the file located by this path for writing, returning an |
|
155 * output stream to write bytes to the file. |
|
156 * |
|
157 * <p> This method returns an {@code OutputStream} that is constructed by |
|
158 * invoking the {@link java.nio.channels.Channels#newOutputStream |
|
159 * Channels.newOutputStream} method. It may be overridden where a more |
|
160 * efficient implementation is available. |
|
161 * |
|
162 * @throws IllegalArgumentException {@inheritDoc} |
|
163 * @throws IOException {@inheritDoc} |
|
164 * @throws SecurityException {@inheritDoc} |
|
165 */ |
|
166 @Override |
|
167 public OutputStream newOutputStream(OpenOption... options) throws IOException { |
|
168 int len = options.length; |
|
169 Set<OpenOption> opts = new HashSet<OpenOption>(len + 3); |
|
170 if (len > 0) { |
|
171 for (OpenOption opt: options) { |
|
172 opts.add(opt); |
|
173 } |
|
174 } |
|
175 return implNewOutputStream(opts); |
|
176 } |
|
177 |
|
178 /** |
|
179 * Opens or creates the file located by this path for writing, returning an |
|
180 * output stream to write bytes to the file. |
|
181 * |
|
182 * <p> This method returns an {@code OutputStream} that is constructed by |
|
183 * invoking the {@link java.nio.channels.Channels#newOutputStream |
|
184 * Channels.newOutputStream} method. It may be overridden where a more |
|
185 * efficient implementation is available. |
|
186 * |
|
187 * @throws IllegalArgumentException {@inheritDoc} |
|
188 * @throws IOException {@inheritDoc} |
|
189 * @throws SecurityException {@inheritDoc} |
|
190 */ |
|
191 @Override |
|
192 public OutputStream newOutputStream(Set<? extends OpenOption> options, |
|
193 FileAttribute<?>... attrs) |
|
194 throws IOException |
|
195 { |
|
196 Set<OpenOption> opts = new HashSet<OpenOption>(options); |
|
197 return implNewOutputStream(opts, attrs); |
|
198 } |
|
199 |
|
200 /** |
|
201 * Opens the directory referenced by this object, returning a {@code |
|
202 * DirectoryStream} to iterate over all entries in the directory. |
|
203 * |
|
204 * <p> This method invokes the {@link |
|
205 * #newDirectoryStream(java.nio.file.DirectoryStream.Filter) |
|
206 * newDirectoryStream(Filter)} method with a filter that accept all entries. |
|
207 * It may be overridden where required. |
|
208 * |
|
209 * @throws NotDirectoryException {@inheritDoc} |
|
210 * @throws IOException {@inheritDoc} |
|
211 * @throws SecurityException {@inheritDoc} |
|
212 */ |
|
213 @Override |
|
214 public DirectoryStream<Path> newDirectoryStream() throws IOException { |
|
215 return newDirectoryStream(acceptAllFilter); |
|
216 } |
|
217 private static final DirectoryStream.Filter<Path> acceptAllFilter = |
|
218 new DirectoryStream.Filter<Path>() { |
|
219 @Override public boolean accept(Path entry) { return true; } |
|
220 }; |
|
221 |
|
222 /** |
|
223 * Opens the directory referenced by this object, returning a {@code |
|
224 * DirectoryStream} to iterate over the entries in the directory. The |
|
225 * entries are filtered by matching the {@code String} representation of |
|
226 * their file names against a given pattern. |
|
227 * |
|
228 * <p> This method constructs a {@link PathMatcher} by invoking the |
|
229 * file system's {@link java.nio.file.FileSystem#getPathMatcher |
|
230 * getPathMatcher} method. This method may be overridden where a more |
|
231 * efficient implementation is available. |
|
232 * |
|
233 * @throws java.util.regex.PatternSyntaxException {@inheritDoc} |
|
234 * @throws UnsupportedOperationException {@inheritDoc} |
|
235 * @throws NotDirectoryException {@inheritDoc} |
|
236 * @throws IOException {@inheritDoc} |
|
237 * @throws SecurityException {@inheritDoc} |
|
238 */ |
|
239 @Override |
|
240 public DirectoryStream<Path> newDirectoryStream(String glob) |
|
241 throws IOException |
|
242 { |
|
243 // avoid creating a matcher if all entries are required. |
|
244 if (glob.equals("*")) |
|
245 return newDirectoryStream(); |
|
246 |
|
247 // create a matcher and return a filter that uses it. |
|
248 final PathMatcher matcher = getFileSystem().getPathMatcher("glob:" + glob); |
|
249 DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() { |
|
250 @Override |
|
251 public boolean accept(Path entry) { |
|
252 return matcher.matches(entry.getName()); |
|
253 } |
|
254 }; |
|
255 return newDirectoryStream(filter); |
|
256 } |
|
257 |
|
258 /** |
|
259 * Tests whether the file located by this path exists. |
|
260 * |
|
261 * <p> This method invokes the {@link #checkAccess checkAccess} method to |
|
262 * check if the file exists. It may be overridden where a more efficient |
|
263 * implementation is available. |
|
264 */ |
|
265 @Override |
|
266 public boolean exists() { |
|
267 try { |
|
268 checkAccess(); |
|
269 return true; |
|
270 } catch (IOException x) { |
|
271 // unable to determine if file exists |
|
272 } |
|
273 return false; |
|
274 } |
|
275 |
|
276 /** |
|
277 * Tests whether the file located by this path does not exist. |
|
278 * |
|
279 * <p> This method invokes the {@link #checkAccess checkAccess} method to |
|
280 * check if the file exists. It may be overridden where a more efficient |
|
281 * implementation is available. |
|
282 */ |
|
283 @Override |
|
284 public boolean notExists() { |
|
285 try { |
|
286 checkAccess(); |
|
287 return false; |
|
288 } catch (NoSuchFileException x) { |
|
289 // file confirmed not to exist |
|
290 return true; |
|
291 } catch (IOException x) { |
|
292 return false; |
|
293 } |
|
294 } |
|
295 |
|
296 /** |
|
297 * Registers the file located by this path with a watch service. |
|
298 * |
|
299 * <p> This method invokes the {@link #register(WatchService,WatchEvent.Kind[],WatchEvent.Modifier[]) |
|
300 * register(WatchService,WatchEvent.Kind[],WatchEvent.Modifier...)} |
|
301 * method to register the file. It may be overridden where required. |
|
302 */ |
|
303 @Override |
|
304 public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) |
|
305 throws IOException |
|
306 { |
|
307 return register(watcher, events, NO_MODIFIERS); |
|
308 } |
|
309 private static final WatchEvent.Modifier[] NO_MODIFIERS = new WatchEvent.Modifier[0]; |
|
310 |
|
311 /** |
|
312 * Copy the file located by this path to a target location. |
|
313 * |
|
314 * <p> This method is invoked by the {@link #copyTo copyTo} method for |
|
315 * the case that this {@code Path} and the target {@code Path} are |
|
316 * associated with the same provider. |
|
317 * |
|
318 * @param target |
|
319 * The target location |
|
320 * @param options |
|
321 * Options specifying how the copy should be done |
|
322 * |
|
323 * @throws IllegalArgumentException |
|
324 * If an invalid option is specified |
|
325 * @throws FileAlreadyExistsException |
|
326 * The target file exists and cannot be replaced because the |
|
327 * {@code REPLACE_EXISTING} option is not specified, or the target |
|
328 * file is a non-empty directory <i>(optional specific exception)</i> |
|
329 * @throws IOException |
|
330 * If an I/O error occurs |
|
331 * @throws SecurityException |
|
332 * In the case of the default provider, and a security manager is |
|
333 * installed, the {@link SecurityManager#checkRead(String) checkRead} |
|
334 * method is invoked to check read access to the source file, the |
|
335 * {@link SecurityManager#checkWrite(String) checkWrite} is invoked |
|
336 * to check write access to the target file. If a symbolic link is |
|
337 * copied the security manager is invoked to check {@link |
|
338 * LinkPermission}{@code ("symbolic")}. |
|
339 */ |
|
340 protected abstract void implCopyTo(Path target, CopyOption... options) |
|
341 throws IOException; |
|
342 |
|
343 /** |
|
344 * Move the file located by this path to a target location. |
|
345 * |
|
346 * <p> This method is invoked by the {@link #moveTo moveTo} method for |
|
347 * the case that this {@code Path} and the target {@code Path} are |
|
348 * associated with the same provider. |
|
349 * |
|
350 * @param target |
|
351 * The target location |
|
352 * @param options |
|
353 * Options specifying how the move should be done |
|
354 * |
|
355 * @throws IllegalArgumentException |
|
356 * If an invalid option is specified |
|
357 * @throws FileAlreadyExistsException |
|
358 * The target file exists and cannot be replaced because the |
|
359 * {@code REPLACE_EXISTING} option is not specified, or the target |
|
360 * file is a non-empty directory |
|
361 * @throws AtomicMoveNotSupportedException |
|
362 * The options array contains the {@code ATOMIC_MOVE} option but |
|
363 * the file cannot be moved as an atomic file system operation. |
|
364 * @throws IOException |
|
365 * If an I/O error occurs |
|
366 * @throws SecurityException |
|
367 * In the case of the default provider, and a security manager is |
|
368 * installed, the {@link SecurityManager#checkWrite(String) checkWrite} |
|
369 * method is invoked to check write access to both the source and |
|
370 * target file. |
|
371 */ |
|
372 protected abstract void implMoveTo(Path target, CopyOption... options) |
|
373 throws IOException; |
|
374 |
|
375 /** |
|
376 * Copy the file located by this path to a target location. |
|
377 * |
|
378 * <p> If this path is associated with the same {@link FileSystemProvider |
|
379 * provider} as the {@code target} then the {@link #implCopyTo implCopyTo} |
|
380 * method is invoked to copy the file. Otherwise, this method attempts to |
|
381 * copy the file to the target location in a manner that may be less |
|
382 * efficient than would be the case that target is associated with the same |
|
383 * provider as this path. |
|
384 * |
|
385 * @throws IllegalArgumentException {@inheritDoc} |
|
386 * @throws FileAlreadyExistsException {@inheritDoc} |
|
387 * @throws IOException {@inheritDoc} |
|
388 * @throws SecurityException {@inheritDoc} |
|
389 */ |
|
390 @Override |
|
391 public final Path copyTo(Path target, CopyOption... options) |
|
392 throws IOException |
|
393 { |
|
394 if ((getFileSystem().provider() == target.getFileSystem().provider())) { |
|
395 implCopyTo(target, options); |
|
396 } else { |
|
397 xProviderCopyTo(target, options); |
|
398 } |
|
399 return target; |
|
400 } |
|
401 |
|
402 /** |
|
403 * Move or rename the file located by this path to a target location. |
|
404 * |
|
405 * <p> If this path is associated with the same {@link FileSystemProvider |
|
406 * provider} as the {@code target} then the {@link #implCopyTo implMoveTo} |
|
407 * method is invoked to move the file. Otherwise, this method attempts to |
|
408 * copy the file to the target location and delete the source file. This |
|
409 * implementation may be less efficient than would be the case that |
|
410 * target is associated with the same provider as this path. |
|
411 * |
|
412 * @throws IllegalArgumentException {@inheritDoc} |
|
413 * @throws FileAlreadyExistsException {@inheritDoc} |
|
414 * @throws IOException {@inheritDoc} |
|
415 * @throws SecurityException {@inheritDoc} |
|
416 */ |
|
417 @Override |
|
418 public final Path moveTo(Path target, CopyOption... options) |
|
419 throws IOException |
|
420 { |
|
421 if ((getFileSystem().provider() == target.getFileSystem().provider())) { |
|
422 implMoveTo(target, options); |
|
423 } else { |
|
424 // different providers so copy + delete |
|
425 xProviderCopyTo(target, convertMoveToCopyOptions(options)); |
|
426 delete(false); |
|
427 } |
|
428 return target; |
|
429 } |
|
430 |
|
431 /** |
|
432 * Converts the given array of options for moving a file to options suitable |
|
433 * for copying the file when a move is implemented as copy + delete. |
|
434 */ |
|
435 private static CopyOption[] convertMoveToCopyOptions(CopyOption... options) |
|
436 throws AtomicMoveNotSupportedException |
|
437 { |
|
438 int len = options.length; |
|
439 CopyOption[] newOptions = new CopyOption[len+2]; |
|
440 for (int i=0; i<len; i++) { |
|
441 CopyOption option = options[i]; |
|
442 if (option == StandardCopyOption.ATOMIC_MOVE) { |
|
443 throw new AtomicMoveNotSupportedException(null, null, |
|
444 "Atomic move between providers is not supported"); |
|
445 } |
|
446 newOptions[i] = option; |
|
447 } |
|
448 newOptions[len] = LinkOption.NOFOLLOW_LINKS; |
|
449 newOptions[len+1] = StandardCopyOption.COPY_ATTRIBUTES; |
|
450 return newOptions; |
|
451 } |
|
452 |
|
453 /** |
|
454 * Parses the arguments for a file copy operation. |
|
455 */ |
|
456 private static class CopyOptions { |
|
457 boolean replaceExisting = false; |
|
458 boolean copyAttributes = false; |
|
459 boolean followLinks = true; |
|
460 |
|
461 private CopyOptions() { } |
|
462 |
|
463 static CopyOptions parse(CopyOption... options) { |
|
464 CopyOptions result = new CopyOptions(); |
|
465 for (CopyOption option: options) { |
|
466 if (option == StandardCopyOption.REPLACE_EXISTING) { |
|
467 result.replaceExisting = true; |
|
468 continue; |
|
469 } |
|
470 if (option == LinkOption.NOFOLLOW_LINKS) { |
|
471 result.followLinks = false; |
|
472 continue; |
|
473 } |
|
474 if (option == StandardCopyOption.COPY_ATTRIBUTES) { |
|
475 result.copyAttributes = true; |
|
476 continue; |
|
477 } |
|
478 if (option == null) |
|
479 throw new NullPointerException(); |
|
480 throw new IllegalArgumentException("'" + option + |
|
481 "' is not a valid copy option"); |
|
482 } |
|
483 return result; |
|
484 } |
|
485 } |
|
486 |
|
487 /** |
|
488 * Simple cross-provider copy where the target is a Path. |
|
489 */ |
|
490 private void xProviderCopyTo(Path target, CopyOption... options) |
|
491 throws IOException |
|
492 { |
|
493 CopyOptions opts = CopyOptions.parse(options); |
|
494 LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] : |
|
495 new LinkOption[] { LinkOption.NOFOLLOW_LINKS }; |
|
496 |
|
497 // attributes of source file |
|
498 BasicFileAttributes attrs = Attributes |
|
499 .readBasicFileAttributes(this, linkOptions); |
|
500 if (attrs.isSymbolicLink()) |
|
501 throw new IOException("Copying of symbolic links not supported"); |
|
502 |
|
503 // delete target file |
|
504 if (opts.replaceExisting) |
|
505 target.delete(false); |
|
506 |
|
507 // create directory or file |
|
508 if (attrs.isDirectory()) { |
|
509 target.createDirectory(); |
|
510 } else { |
|
511 xProviderCopyRegularFileTo(target); |
|
512 } |
|
513 |
|
514 // copy basic attributes to target |
|
515 if (opts.copyAttributes) { |
|
516 BasicFileAttributeView view = target |
|
517 .getFileAttributeView(BasicFileAttributeView.class, linkOptions); |
|
518 try { |
|
519 view.setTimes(attrs.lastModifiedTime(), |
|
520 attrs.lastAccessTime(), |
|
521 attrs.creationTime(), |
|
522 attrs.resolution()); |
|
523 } catch (IOException x) { |
|
524 // rollback |
|
525 try { |
|
526 target.delete(false); |
|
527 } catch (IOException ignore) { } |
|
528 throw x; |
|
529 } |
|
530 } |
|
531 } |
|
532 |
|
533 |
|
534 /** |
|
535 * Simple copy of regular file to a target file that exists. |
|
536 */ |
|
537 private void xProviderCopyRegularFileTo(FileRef target) |
|
538 throws IOException |
|
539 { |
|
540 ReadableByteChannel rbc = newByteChannel(); |
|
541 try { |
|
542 // open target file for writing |
|
543 SeekableByteChannel sbc = target.newByteChannel(CREATE, WRITE); |
|
544 |
|
545 // simple copy loop |
|
546 try { |
|
547 ByteBuffer buf = ByteBuffer.wrap(new byte[8192]); |
|
548 int n = 0; |
|
549 for (;;) { |
|
550 n = rbc.read(buf); |
|
551 if (n < 0) |
|
552 break; |
|
553 assert n > 0; |
|
554 buf.flip(); |
|
555 while (buf.hasRemaining()) { |
|
556 sbc.write(buf); |
|
557 } |
|
558 buf.rewind(); |
|
559 } |
|
560 |
|
561 } finally { |
|
562 sbc.close(); |
|
563 } |
|
564 } finally { |
|
565 rbc.close(); |
|
566 } |
|
567 } |
|
568 } |