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 }