/*
 * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.nio.fs;

import java.nio.file.*;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.*;
import java.io.IOException;
import static sun.nio.fs.UnixNativeDispatcher.*;

/**
 * Unix implementation of java.nio.file.DirectoryStream
 */

class UnixDirectoryStream
    implements DirectoryStream<Path>
{
    // path to directory when originally opened
    private final UnixPath dir;

    // directory pointer (returned by opendir)
    private final long dp;

    // filter (may be null)
    private final DirectoryStream.Filter<? super Path> filter;

    // used to coorindate closing of directory stream
    private final ReentrantReadWriteLock streamLock =
        new ReentrantReadWriteLock(true);

    // indicates if directory stream is open (synchronize on closeLock)
    private volatile boolean isClosed;

    // directory iterator
    private Iterator<Path> iterator;

    /**
     * Initializes a new instance
     */
    UnixDirectoryStream(UnixPath dir, long dp, DirectoryStream.Filter<? super Path> filter) {
        this.dir = dir;
        this.dp = dp;
        this.filter = filter;
    }

    protected final UnixPath directory() {
        return dir;
    }

    protected final Lock readLock() {
        return streamLock.readLock();
    }

    protected final Lock writeLock() {
        return streamLock.writeLock();
    }

    protected final boolean isOpen() {
        return !isClosed;
    }

    protected final boolean closeImpl() throws IOException {
        if (!isClosed) {
            isClosed = true;
            try {
                closedir(dp);
            } catch (UnixException x) {
                throw new IOException(x.errorString());
            }
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void close()
        throws IOException
    {
        writeLock().lock();
        try {
            closeImpl();
        } finally {
            writeLock().unlock();
        }
    }

    protected final Iterator<Path> iterator(DirectoryStream<Path> ds) {
        if (isClosed) {
            throw new IllegalStateException("Directory stream is closed");
        }
        synchronized (this) {
            if (iterator != null)
                throw new IllegalStateException("Iterator already obtained");
            iterator = new UnixDirectoryIterator(ds);
            return iterator;
        }
    }

    @Override
    public Iterator<Path> iterator() {
        return iterator(this);
    }

    /**
     * Iterator implementation
     */
    private class UnixDirectoryIterator implements Iterator<Path> {
        private final DirectoryStream<Path> stream;

        // true when at EOF
        private boolean atEof;

        // next entry to return
        private Path nextEntry;

        UnixDirectoryIterator(DirectoryStream<Path> stream) {
            atEof = false;
            this.stream = stream;
        }

        // Return true if file name is "." or ".."
        private boolean isSelfOrParent(byte[] nameAsBytes) {
            if (nameAsBytes[0] == '.') {
                if ((nameAsBytes.length == 1) ||
                    (nameAsBytes.length == 2 && nameAsBytes[1] == '.')) {
                    return true;
                }
            }
            return false;
        }

        // Returns next entry (or null)
        private Path readNextEntry() {
            assert Thread.holdsLock(this);

            for (;;) {
                byte[] nameAsBytes = null;

                // prevent close while reading
                readLock().lock();
                try {
                    if (isOpen()) {
                        nameAsBytes = readdir(dp);
                    }
                } catch (UnixException x) {
                    IOException ioe = x.asIOException(dir);
                    throw new DirectoryIteratorException(ioe);
                } finally {
                    readLock().unlock();
                }

                // EOF
                if (nameAsBytes == null) {
                    atEof = true;
                    return null;
                }

                // ignore "." and ".."
                if (!isSelfOrParent(nameAsBytes)) {
                    Path entry = dir.resolve(nameAsBytes);

                    // return entry if no filter or filter accepts it
                    try {
                        if (filter == null || filter.accept(entry))
                            return entry;
                    } catch (IOException ioe) {
                        throw new DirectoryIteratorException(ioe);
                    }
                }
            }
        }

        @Override
        public synchronized boolean hasNext() {
            if (nextEntry == null && !atEof)
                nextEntry = readNextEntry();
            return nextEntry != null;
        }

        @Override
        public synchronized Path next() {
            Path result;
            if (nextEntry == null && !atEof) {
                result = readNextEntry();
            } else {
                result = nextEntry;
                nextEntry = null;
            }
            if (result == null)
                throw new NoSuchElementException();
            return result;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}
