View Javadoc
1   /*
2    * Copyright (C) 2005-2015 Schlichtherle IT Services.
3    * All rights reserved. Use is subject to license terms.
4    */
5   package net.java.truevfs.driver.file;
6   
7   import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
8   import net.java.truecommons.cio.Entry;
9   import net.java.truecommons.cio.InputSocket;
10  import net.java.truecommons.cio.IoBuffer;
11  import net.java.truecommons.cio.OutputSocket;
12  import net.java.truecommons.shed.BitField;
13  import net.java.truevfs.kernel.spec.FsAbstractNode;
14  import net.java.truevfs.kernel.spec.FsAccessOption;
15  import net.java.truevfs.kernel.spec.FsNode;
16  import net.java.truevfs.kernel.spec.FsNodeName;
17  
18  import javax.annotation.CheckForNull;
19  import javax.annotation.Nullable;
20  import javax.annotation.concurrent.Immutable;
21  import java.io.IOException;
22  import java.nio.file.DirectoryStream;
23  import java.nio.file.Path;
24  import java.nio.file.Paths;
25  import java.nio.file.attribute.BasicFileAttributes;
26  import java.nio.file.attribute.PosixFilePermission;
27  import java.util.LinkedHashSet;
28  import java.util.Set;
29  
30  import static java.io.File.separatorChar;
31  import static java.nio.file.Files.*;
32  import static java.nio.file.attribute.PosixFilePermission.*;
33  import static net.java.truevfs.kernel.spec.FsAccessOptions.NONE;
34  import static net.java.truevfs.kernel.spec.FsNodeName.SEPARATOR_CHAR;
35  
36  /**
37   * Adapts a {@link Path} instance to a {@link FsNode}.
38   *
39   * @author Christian Schlichtherle
40   */
41  @Immutable
42  class FileNode extends FsAbstractNode implements IoBuffer {
43  
44      private static final Path CURRENT_DIRECTORY = Paths.get(".");
45  
46      private final Path path;
47      private final String name;
48  
49      @SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
50      volatile @CheckForNull FileBufferPool pool;
51  
52      FileNode(final Path path) {
53          assert null != path;
54          this.path = path;
55          this.name = path.toString(); // deliberately breaks contract for FsNode.getName()
56      }
57  
58      FileNode(final Path path, final FsNodeName name) {
59          assert null != path;
60          this.path = path.resolve(name.getPath());
61          this.name = name.toString();
62      }
63  
64      private BasicFileAttributes readBasicFileAttributes() throws IOException {
65          return readAttributes(path, BasicFileAttributes.class);
66      }
67  
68      final FileNode createIoBuffer() throws IOException {
69          FileBufferPool pool = this.pool;
70          if (null == pool)
71              this.pool = pool = new FileBufferPool(getParent(), getFileName());
72          return pool.allocate();
73      }
74  
75      private Path getParent() {
76          final Path path = this.path.getParent();
77          return null != path ? path : CURRENT_DIRECTORY;
78      }
79  
80      private String getFileName() {
81          // See http://java.net/jira/browse/TRUEZIP-152
82          final Path path = this.path.getFileName();
83          return null != path ? path.toString() : "";
84      }
85  
86      @Override public void release() throws IOException { }
87  
88      /** Returns the decorated file. */
89      final Path getPath() {
90          return path;
91      }
92  
93      @Override
94      public final String getName() {
95          return name.replace(separatorChar, SEPARATOR_CHAR); // postfix
96      }
97  
98      @Override
99      public final BitField<Type> getTypes() {
100         try {
101             final BasicFileAttributes attr = readBasicFileAttributes();
102             if      (attr.isRegularFile())  return FILE_TYPE;
103             else if (attr.isDirectory())    return DIRECTORY_TYPE;
104             else if (attr.isSymbolicLink()) return SYMLINK_TYPE;
105             else if (attr.isOther())        return SPECIAL_TYPE;
106         } catch (IOException ignore) {
107             // This doesn't exist or may be inaccessible. In either case...
108         }
109         return NO_TYPES;
110     }
111 
112     @Override
113     public final boolean isType(final Type type) {
114         try {
115             switch (type) {
116             case FILE:      return readBasicFileAttributes().isRegularFile();
117             case DIRECTORY: return readBasicFileAttributes().isDirectory();
118             case SYMLINK:   return readBasicFileAttributes().isSymbolicLink();
119             case SPECIAL:   return readBasicFileAttributes().isOther();
120             }
121         } catch (IOException ignored) {
122         }
123         return false;
124     }
125 
126     @Override
127     public final long getSize(final Size type) {
128         try {
129             return readBasicFileAttributes().size();
130         } catch (IOException ignore) {
131             // This doesn't exist or may be inaccessible. In either case...
132             return UNKNOWN;
133         }
134     }
135 
136     @Override
137     public final long getTime(Access type) {
138         try {
139             final BasicFileAttributes attr = readBasicFileAttributes();
140             switch (type) {
141             case CREATE: return attr.creationTime().toMillis();
142             case READ:   return attr.lastAccessTime().toMillis();
143             case WRITE:  return attr.lastModifiedTime().toMillis();
144             }
145         } catch (IOException ignore) {
146             // This doesn't exist or may be inaccessible. In either case...
147         }
148         return UNKNOWN;
149     }
150 
151     @Override
152     @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
153     public Boolean isPermitted(final Access type, final Entity entity) {
154         if (!(entity instanceof PosixEntity)) return null;
155         try {
156             final Set<PosixFilePermission> permissions = getPosixFilePermissions(path);
157             switch ((PosixEntity) entity) {
158             case USER:
159                 switch (type) {
160                 case READ:    return permissions.contains(OWNER_READ);
161                 case WRITE:   return permissions.contains(OWNER_WRITE);
162                 case EXECUTE: return permissions.contains(OWNER_EXECUTE);
163                 }
164                 break;
165             case GROUP:
166                 switch (type) {
167                 case READ:    return permissions.contains(GROUP_READ);
168                 case WRITE:   return permissions.contains(GROUP_WRITE);
169                 case EXECUTE: return permissions.contains(GROUP_EXECUTE);
170                 }
171                 break;
172             case OTHER:
173                 switch (type) {
174                 case READ:    return permissions.contains(OTHERS_READ);
175                 case WRITE:   return permissions.contains(OTHERS_WRITE);
176                 case EXECUTE: return permissions.contains(OTHERS_EXECUTE);
177                 }
178             }
179         } catch (UnsupportedOperationException | IOException ignore) {
180             // Unsupported, doesn't exist or inaccessible. In either case...
181         }
182         return null;
183     }
184 
185     @Override
186     public final @Nullable Set<String> getMembers() {
187         try {
188             try (final DirectoryStream<Path> stream = newDirectoryStream(path)) {
189                 final Set<String> result = new LinkedHashSet<>();
190                 for (final Path member : stream)
191                     result.add(member.getFileName().toString());
192                 return result;
193             }
194         } catch (IOException ignore) {
195             // This isn't a directory or may be inaccessible. In either case...
196             return null;
197         }
198     }
199 
200     @Override
201     public final InputSocket<FileNode> input() {
202         return input(NONE);
203     }
204 
205     final InputSocket<FileNode> input(BitField<FsAccessOption> options) {
206         return new FileInputSocket(options, this);
207     }
208 
209     @Override
210     public final OutputSocket<FileNode> output() {
211         return output(NONE, null);
212     }
213 
214     final OutputSocket<FileNode> output(
215             BitField<FsAccessOption> options,
216             @CheckForNull Entry template) {
217         return new FileOutputSocket(options, this, template);
218     }
219 }