1 /*
   2  * Copyright (c) 2008, 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 java.util.Iterator;
  30 import java.util.NoSuchElementException;
  31 import java.util.concurrent.locks.*;
  32 import java.io.IOException;
  33 import static sun.nio.fs.UnixNativeDispatcher.*;
  34 
  35 /**
  36  * Unix implementation of java.nio.file.DirectoryStream
  37  */
  38 
  39 class UnixDirectoryStream
  40     implements DirectoryStream<Path>
  41 {
  42     // path to directory when originally opened
  43     private final UnixPath dir;
  44 
  45     // directory pointer (returned by opendir)
  46     private final long dp;
  47 
  48     // filter (may be null)
  49     private final DirectoryStream.Filter<? super Path> filter;
  50 
  51     // used to coorindate closing of directory stream
  52     private final ReentrantReadWriteLock streamLock =
  53         new ReentrantReadWriteLock(true);
  54 
  55     // indicates if directory stream is open (synchronize on closeLock)
  56     private volatile boolean isClosed;
  57 
  58     // directory iterator
  59     private Iterator<Path> iterator;
  60 
  61     /**
  62      * Initializes a new instance
  63      */
  64     UnixDirectoryStream(UnixPath dir, long dp, DirectoryStream.Filter<? super Path> filter) {
  65         this.dir = dir;
  66         this.dp = dp;
  67         this.filter = filter;
  68     }
  69 
  70     protected final UnixPath directory() {
  71         return dir;
  72     }
  73 
  74     protected final Lock readLock() {
  75         return streamLock.readLock();
  76     }
  77 
  78     protected final Lock writeLock() {
  79         return streamLock.writeLock();
  80     }
  81 
  82     protected final boolean isOpen() {
  83         return !isClosed;
  84     }
  85 
  86     protected final boolean closeImpl() throws IOException {
  87         if (!isClosed) {
  88             isClosed = true;
  89             try {
  90                 closedir(dp);
  91             } catch (UnixException x) {
  92                 throw new IOException(x.errorString());
  93             }
  94             return true;
  95         } else {
  96             return false;
  97         }
  98     }
  99 
 100     @Override
 101     public void close()
 102         throws IOException
 103     {
 104         writeLock().lock();
 105         try {
 106             closeImpl();
 107         } finally {
 108             writeLock().unlock();
 109         }
 110     }
 111 
 112     protected final Iterator<Path> iterator(DirectoryStream<Path> ds) {
 113         if (isClosed) {
 114             throw new IllegalStateException("Directory stream is closed");
 115         }
 116         synchronized (this) {
 117             if (iterator != null)
 118                 throw new IllegalStateException("Iterator already obtained");
 119             iterator = new UnixDirectoryIterator(ds);
 120             return iterator;
 121         }
 122     }
 123 
 124     @Override
 125     public Iterator<Path> iterator() {
 126         return iterator(this);
 127     }
 128 
 129     /**
 130      * Iterator implementation
 131      */
 132     private class UnixDirectoryIterator implements Iterator<Path> {
 133         private final DirectoryStream<Path> stream;
 134 
 135         // true when at EOF
 136         private boolean atEof;
 137 
 138         // next entry to return
 139         private Path nextEntry;
 140 
 141         UnixDirectoryIterator(DirectoryStream<Path> stream) {
 142             atEof = false;
 143             this.stream = stream;
 144         }
 145 
 146         // Return true if file name is "." or ".."
 147         private boolean isSelfOrParent(byte[] nameAsBytes) {
 148             if (nameAsBytes[0] == '.') {
 149                 if ((nameAsBytes.length == 1) ||
 150                     (nameAsBytes.length == 2 && nameAsBytes[1] == '.')) {
 151                     return true;
 152                 }
 153             }
 154             return false;
 155         }
 156 
 157         // Returns next entry (or null)
 158         private Path readNextEntry() {
 159             assert Thread.holdsLock(this);
 160 
 161             for (;;) {
 162                 byte[] nameAsBytes = null;
 163 
 164                 // prevent close while reading
 165                 readLock().lock();
 166                 try {
 167                     if (isOpen()) {
 168                         nameAsBytes = readdir(dp);
 169                     }
 170                 } catch (UnixException x) {
 171                     IOException ioe = x.asIOException(dir);
 172                     throw new DirectoryIteratorException(ioe);
 173                 } finally {
 174                     readLock().unlock();
 175                 }
 176 
 177                 // EOF
 178                 if (nameAsBytes == null) {
 179                     atEof = true;
 180                     return null;
 181                 }
 182 
 183                 // ignore "." and ".."
 184                 if (!isSelfOrParent(nameAsBytes)) {
 185                     Path entry = dir.resolve(nameAsBytes);
 186 
 187                     // return entry if no filter or filter accepts it
 188                     try {
 189                         if (filter == null || filter.accept(entry))
 190                             return entry;
 191                     } catch (IOException ioe) {
 192                         throw new DirectoryIteratorException(ioe);
 193                     }
 194                 }
 195             }
 196         }
 197 
 198         @Override
 199         public synchronized boolean hasNext() {
 200             if (nextEntry == null && !atEof)
 201                 nextEntry = readNextEntry();
 202             return nextEntry != null;
 203         }
 204 
 205         @Override
 206         public synchronized Path next() {
 207             Path result;
 208             if (nextEntry == null && !atEof) {
 209                 result = readNextEntry();
 210             } else {
 211                 result = nextEntry;
 212                 nextEntry = null;
 213             }
 214             if (result == null)
 215                 throw new NoSuchElementException();
 216             return result;
 217         }
 218 
 219         @Override
 220         public void remove() {
 221             throw new UnsupportedOperationException();
 222         }
 223     }
 224 }