1/*
2 * Copyright (c) 2013, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.io.IOException;
25import java.net.URI;
26import java.nio.channels.SeekableByteChannel;
27import java.nio.file.AccessMode;
28import java.nio.file.CopyOption;
29import java.nio.file.DirectoryIteratorException;
30import java.nio.file.DirectoryStream;
31import java.nio.file.FileStore;
32import java.nio.file.FileSystem;
33import java.nio.file.FileSystemAlreadyExistsException;
34import java.nio.file.FileSystemNotFoundException;
35import java.nio.file.Files;
36import java.nio.file.LinkOption;
37import java.nio.file.NoSuchFileException;
38import java.nio.file.OpenOption;
39import java.nio.file.Path;
40import java.nio.file.PathMatcher;
41import java.nio.file.WatchService;
42import java.nio.file.attribute.BasicFileAttributes;
43import java.nio.file.attribute.FileAttribute;
44import java.nio.file.attribute.FileAttributeView;
45import java.nio.file.attribute.UserPrincipalLookupService;
46import java.nio.file.spi.FileSystemProvider;
47import java.util.Iterator;
48import java.util.Map;
49import java.util.NoSuchElementException;
50import java.util.Set;
51import java.util.function.Supplier;
52
53/**
54 * A {@code FileSystem} that helps testing by trigger exception throwing based on filenames.
55 */
56class FaultyFileSystem extends FileSystem {
57    final Path root;
58    final boolean removeRootAfterClose;
59    final FileSystem delegate;
60    boolean isOpen;
61
62    FaultyFileSystem(Path root) throws IOException {
63        if (root == null) {
64            root = Files.createTempDirectory("faultyFS");
65            removeRootAfterClose = true;
66        } else {
67            if (! Files.isDirectory(root)) {
68                throw new IllegalArgumentException("must be a directory.");
69            }
70            removeRootAfterClose = false;
71        }
72        this.root = root;
73        delegate = root.getFileSystem();
74        isOpen = true;
75    }
76
77    private static Path unwrap(Path p) {
78        return PassThroughFileSystem.unwrap(p);
79    }
80
81    Path getRoot() {
82        return new PassThroughFileSystem.PassThroughPath(this, root);
83    }
84
85    @Override
86    public void close() throws IOException {
87        if (isOpen) {
88            if (removeRootAfterClose) {
89                TestUtil.removeAll(root);
90            }
91            isOpen = false;
92        }
93    }
94
95    @Override
96    public FileSystemProvider provider() {
97        return FaultyFSProvider.getInstance();
98    }
99
100    @Override
101    public boolean isOpen() {
102        return isOpen;
103    }
104
105    @Override
106    public boolean isReadOnly() {
107        return delegate.isReadOnly();
108    }
109
110    @Override
111    public String getSeparator() {
112        return delegate.getSeparator();
113    }
114
115    private <T> Iterable<T> SoleIterable(final T element) {
116        return new Iterable<T>() {
117            @Override
118            public Iterator<T> iterator() {
119                return new Iterator<T>() {
120                    private T soleElement = element;
121
122                    @Override
123                    public boolean hasNext() {
124                        return soleElement != null;
125                    }
126
127                    @Override
128                    public T next() {
129                        try {
130                            return soleElement;
131                        } finally {
132                            soleElement = null;
133                        }
134                    }
135                };
136            }
137        };
138    }
139
140    @Override
141    public Iterable<Path> getRootDirectories() {
142        return SoleIterable(getRoot());
143    }
144
145    @Override
146    public Iterable<FileStore> getFileStores() {
147        FileStore store;
148        try {
149            store = Files.getFileStore(root);
150        } catch (IOException ioe) {
151            store = null;
152        }
153        return SoleIterable(store);
154    }
155
156    @Override
157    public Set<String> supportedFileAttributeViews() {
158        // assume that unwrapped objects aren't exposed
159        return delegate.supportedFileAttributeViews();
160    }
161
162    @Override
163    public Path getPath(String first, String... more) {
164        return new PassThroughFileSystem.PassThroughPath(this, delegate.getPath(first, more));
165    }
166
167    @Override
168    public PathMatcher getPathMatcher(String syntaxAndPattern) {
169        final PathMatcher matcher = delegate.getPathMatcher(syntaxAndPattern);
170        return new PathMatcher() {
171            @Override
172            public boolean matches(Path path) {
173                return matcher.matches(unwrap(path));
174            }
175        };
176    }
177
178    @Override
179    public UserPrincipalLookupService getUserPrincipalLookupService() {
180        // assume that unwrapped objects aren't exposed
181        return delegate.getUserPrincipalLookupService();
182    }
183
184    @Override
185    public WatchService newWatchService() throws IOException {
186        // to keep it simple
187        throw new UnsupportedOperationException();
188    }
189
190    static class FaultyException extends IOException {
191        FaultyException() {
192            super("fault triggered.");
193        }
194    }
195
196    static class FaultyFSProvider extends FileSystemProvider {
197        private static final String SCHEME = "faulty";
198        private static volatile FaultyFileSystem delegate;
199        private static FaultyFSProvider INSTANCE = new FaultyFSProvider();
200        private boolean enabled;
201
202        private FaultyFSProvider() {}
203
204        public static FaultyFSProvider getInstance() {
205            return INSTANCE;
206        }
207
208        public void setFaultyMode(boolean enable) {
209            enabled = enable;
210        }
211
212        private void triggerEx(String filename, String... names) throws IOException {
213            if (! enabled) {
214                return;
215            }
216
217            if (filename.equals("SecurityException")) {
218                throw new SecurityException("FaultyFS", new FaultyException());
219            }
220
221            if (filename.equals("IOException")) {
222                throw new FaultyException();
223            }
224
225            for (String name: names) {
226                if (name.equals(filename)) {
227                    throw new FaultyException();
228                }
229            }
230        }
231
232        private void triggerEx(Path path, String... names) throws IOException {
233            triggerEx(path.getFileName().toString(), names);
234        }
235
236        @Override
237        public String getScheme() {
238            return SCHEME;
239        }
240
241        private void checkScheme(URI uri) {
242            if (!uri.getScheme().equalsIgnoreCase(SCHEME))
243                throw new IllegalArgumentException();
244        }
245
246        private void checkUri(URI uri) {
247            checkScheme(uri);
248            if (!uri.getSchemeSpecificPart().equals("///"))
249                throw new IllegalArgumentException();
250        }
251
252        @Override
253        public FileSystem newFileSystem(Path fakeRoot, Map<String,?> env)
254            throws IOException
255        {
256            if (env != null && env.keySet().contains("IOException")) {
257                triggerEx("IOException");
258            }
259
260            synchronized (FaultyFSProvider.class) {
261                if (delegate != null && delegate.isOpen())
262                    throw new FileSystemAlreadyExistsException();
263                FaultyFileSystem result = new FaultyFileSystem(fakeRoot);
264                delegate = result;
265                return result;
266            }
267        }
268
269        @Override
270        public FileSystem newFileSystem(URI uri, Map<String,?> env)
271            throws IOException
272        {
273            if (env != null && env.keySet().contains("IOException")) {
274                triggerEx("IOException");
275            }
276
277            checkUri(uri);
278            synchronized (FaultyFSProvider.class) {
279                if (delegate != null && delegate.isOpen())
280                    throw new FileSystemAlreadyExistsException();
281                FaultyFileSystem result = new FaultyFileSystem(null);
282                delegate = result;
283                return result;
284            }
285        }
286
287        @Override
288        public FileSystem getFileSystem(URI uri) {
289            checkUri(uri);
290            FileSystem result = delegate;
291            if (result == null)
292                throw new FileSystemNotFoundException();
293            return result;
294        }
295
296        @Override
297        public Path getPath(URI uri) {
298            checkScheme(uri);
299            if (delegate == null)
300                throw new FileSystemNotFoundException();
301
302            // only allow absolute path
303            String path = uri.getSchemeSpecificPart();
304            if (! path.startsWith("///")) {
305                throw new IllegalArgumentException();
306            }
307            return new PassThroughFileSystem.PassThroughPath(delegate, delegate.root.resolve(path.substring(3)));
308        }
309
310        @Override
311        public void setAttribute(Path file, String attribute, Object value, LinkOption... options)
312            throws IOException
313        {
314            triggerEx(file, "setAttribute");
315            Files.setAttribute(unwrap(file), attribute, value, options);
316        }
317
318        @Override
319        public Map<String,Object> readAttributes(Path file, String attributes, LinkOption... options)
320            throws IOException
321        {
322            triggerEx(file, "readAttributes");
323            return Files.readAttributes(unwrap(file), attributes, options);
324        }
325
326        @Override
327        public <V extends FileAttributeView> V getFileAttributeView(Path file,
328                                                                    Class<V> type,
329                                                                    LinkOption... options)
330        {
331            return Files.getFileAttributeView(unwrap(file), type, options);
332        }
333
334        @Override
335        public <A extends BasicFileAttributes> A readAttributes(Path file,
336                                                                Class<A> type,
337                                                                LinkOption... options)
338            throws IOException
339        {
340            triggerEx(file, "readAttributes");
341            return Files.readAttributes(unwrap(file), type, options);
342        }
343
344        @Override
345        public void delete(Path file) throws IOException {
346            triggerEx(file, "delete");
347            Files.delete(unwrap(file));
348        }
349
350        @Override
351        public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)
352            throws IOException
353        {
354            triggerEx(target, "createSymbolicLink");
355            Files.createSymbolicLink(unwrap(link), unwrap(target), attrs);
356        }
357
358        @Override
359        public void createLink(Path link, Path existing) throws IOException {
360            triggerEx(existing, "createLink");
361            Files.createLink(unwrap(link), unwrap(existing));
362        }
363
364        @Override
365        public Path readSymbolicLink(Path link) throws IOException {
366            Path target = Files.readSymbolicLink(unwrap(link));
367            triggerEx(target, "readSymbolicLink");
368            return new PassThroughFileSystem.PassThroughPath(delegate, target);
369        }
370
371
372        @Override
373        public void copy(Path source, Path target, CopyOption... options) throws IOException {
374            triggerEx(source, "copy");
375            Files.copy(unwrap(source), unwrap(target), options);
376        }
377
378        @Override
379        public void move(Path source, Path target, CopyOption... options) throws IOException {
380            triggerEx(source, "move");
381            Files.move(unwrap(source), unwrap(target), options);
382        }
383
384        private DirectoryStream<Path> wrap(final DirectoryStream<Path> stream) {
385            return new DirectoryStream<Path>() {
386                @Override
387                public Iterator<Path> iterator() {
388                    final Iterator<Path> itr = stream.iterator();
389                    return new Iterator<Path>() {
390                        private Path next = null;
391                        @Override
392                        public boolean hasNext() {
393                            if (next == null) {
394                                if (itr.hasNext()) {
395                                    next = itr.next();
396                                } else {
397                                    return false;
398                                }
399                            }
400                            if (next != null) {
401                                try {
402                                    triggerEx(next, "DirectoryIteratorException");
403                                } catch (IOException ioe) {
404                                    throw new DirectoryIteratorException(ioe);
405                                } catch (SecurityException se) {
406                                    // ??? Does DS throw SecurityException during iteration?
407                                    next = null;
408                                    return hasNext();
409                                }
410                            }
411                            return (next != null);
412                        }
413                        @Override
414                        public Path next() {
415                            try {
416                                if (next != null || hasNext()) {
417                                    return new PassThroughFileSystem.PassThroughPath(delegate, next);
418                                } else {
419                                    throw new NoSuchElementException();
420                                }
421                            } finally {
422                                next = null;
423                            }
424                        }
425
426                        @Override
427                        public void remove() {
428                            itr.remove();
429                        }
430                    };
431                }
432                @Override
433                public void close() throws IOException {
434                    stream.close();
435                }
436            };
437        }
438
439        @Override
440        public DirectoryStream<Path> newDirectoryStream(Path dir, DirectoryStream.Filter<? super Path> filter)
441            throws IOException
442        {
443            triggerEx(dir, "newDirectoryStream");
444            return wrap(Files.newDirectoryStream(unwrap(dir), filter));
445        }
446
447        @Override
448        public void createDirectory(Path dir, FileAttribute<?>... attrs)
449            throws IOException
450        {
451            triggerEx(dir, "createDirectory");
452            Files.createDirectory(unwrap(dir), attrs);
453        }
454
455        @Override
456        public SeekableByteChannel newByteChannel(Path file,
457                                                  Set<? extends OpenOption> options,
458                                                  FileAttribute<?>... attrs)
459            throws IOException
460        {
461            triggerEx(file, "newByteChannel");
462            return Files.newByteChannel(unwrap(file), options, attrs);
463        }
464
465
466        @Override
467        public boolean isHidden(Path file) throws IOException {
468            triggerEx(file, "isHidden");
469            return Files.isHidden(unwrap(file));
470        }
471
472        @Override
473        public FileStore getFileStore(Path file) throws IOException {
474            triggerEx(file, "getFileStore");
475            return Files.getFileStore(unwrap(file));
476        }
477
478        @Override
479        public boolean isSameFile(Path file, Path other) throws IOException {
480            triggerEx(file, "isSameFile");
481            return Files.isSameFile(unwrap(file), unwrap(other));
482        }
483
484        @Override
485        public void checkAccess(Path file, AccessMode... modes)
486            throws IOException
487        {
488            triggerEx(file, "checkAccess");
489            // hack
490            if (modes.length == 0) {
491                if (Files.exists(unwrap(file)))
492                    return;
493                else
494                    throw new NoSuchFileException(file.toString());
495            }
496            throw new RuntimeException("not implemented yet");
497        }
498    }
499}
500