View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.datamodel.ifs2;
20  
21  import java.io.IOException;
22  import java.nio.channels.SeekableByteChannel;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.StandardOpenOption;
26  import java.nio.file.attribute.BasicFileAttributes;
27  import java.util.Date;
28  import java.util.StringTokenizer;
29  import java.util.stream.Stream;
30  
31  import org.mycore.common.content.MCRContent;
32  import org.mycore.common.content.MCRPathContent;
33  
34  /**
35   * Represents a file, directory or file collection within a file store. Files
36   * and directories can be either really stored, or virtually existing as a child
37   * node contained within a stored container file like zip or tar.
38   * 
39   * @author Frank Lützenkirchen
40   */
41  public abstract class MCRNode {
42      /**
43       * The path object representing this node in the underlying filesystem.
44       */
45      protected Path path;
46  
47      /**
48       * The parent node owning this file, a directory or container file
49       */
50      protected MCRNode parent;
51  
52      /**
53       * Creates a new node representing a child of the given parent
54       * 
55       * @param parent
56       *            the parent node
57       * @param path
58       *            the file object representing this node in the underlying
59       *            filesystem
60       */
61      protected MCRNode(MCRNode parent, Path path) {
62          this.path = path;
63          this.parent = parent;
64      }
65  
66      /**
67       * Returns the file or directory name
68       * 
69       * @return the node's filename
70       */
71      public String getName() {
72          return path.getFileName().toString();
73      }
74  
75      /**
76       * Returns the complete path of this node up to the root file collection.
77       * Path always start with a slash, slash is used as directory delimiter.
78       * 
79       * @return the absolute path of this node
80       */
81      public String getPath() {
82          if (parent != null) {
83              if (parent.parent == null) {
84                  return "/" + getName();
85              } else {
86                  return parent.getPath() + "/" + getName();
87              }
88          } else {
89              return "/";
90          }
91      }
92  
93      /**
94       * Returns the parent node containing this node
95       * 
96       * @return the parent directory or container file
97       */
98      public MCRNode getParent() {
99          return parent;
100     }
101 
102     /**
103      * Returns the root file collection this node belongs to
104      * 
105      * @return the root file collection
106      */
107     public MCRFileCollection getRoot() {
108         return parent.getRoot();
109     }
110 
111     /**
112      * Returns true if this node is a file
113      * 
114      * @return true if this node is a file
115      */
116     public boolean isFile() throws IOException {
117         return Files.isRegularFile(path);
118     }
119 
120     /**
121      * Returns true if this node is a directory
122      * 
123      * @return true if this node is a directory
124      */
125     public boolean isDirectory() throws IOException {
126         return Files.isDirectory(path);
127     }
128 
129     /**
130      * For file nodes, returns the file content size in bytes, otherwise returns
131      * 0.
132      * 
133      * @return the file size in bytes
134      */
135     public long getSize() throws IOException {
136         BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
137         return attr.isRegularFile() ? attr.size() : 0;
138     }
139 
140     /**
141      * Returns the time this node was last modified.
142      * 
143      * @return the time this node was last modified
144      */
145     public Date getLastModified() throws IOException {
146         return Date.from(Files.getLastModifiedTime(path).toInstant());
147     }
148 
149     /**
150      * Returns true if this node has child nodes. Directories and container
151      * files like zip or tar may have child nodes.
152      * 
153      * @return true if children exist
154      */
155     public boolean hasChildren() throws IOException {
156         return !isFile() && Files.list(path).findAny().isPresent();
157     }
158 
159     /**
160      * Returns the number of child nodes of this node.
161      * 
162      * @return the number of child nodes of this node.
163      */
164     public int getNumChildren() throws IOException {
165         if (isFile()) {
166             return 0;
167         }
168 
169         try (Stream<Path> streamPath = Files.list(path)) {
170             return Math.toIntExact(streamPath.count());
171         }
172     }
173 
174     /**
175      * Returns the children of this node.
176      * 
177      * @return a List of child nodes, which may be empty, in undefined order
178      */
179     public Stream<MCRNode> getChildren() throws IOException {
180         if (isFile()) {
181             return Stream.empty();
182         }
183         return Files.list(path)
184             .map(child -> buildChildNode(child));
185     }
186 
187     /**
188      * Creates a node instance for the given FileObject, which represents the
189      * child
190      * 
191      * @param fo
192      *            the FileObject representing the child in the underlying
193      *            filesystem
194      * @return the child node or null, if the fo does not exists
195      * @throws IllegalArgumentException if fo is not valid path for a child of this
196      */
197     protected abstract MCRNode buildChildNode(Path fo);
198 
199     /**
200      * Returns the child node with the given filename, or null
201      * 
202      * @param name
203      *            the name of the child node
204      * @return the child node with that name, or null when no such file exists
205      */
206     public MCRNode getChild(String name) {
207         Path child = path.resolve(name);
208         return buildChildNode(child);
209     }
210 
211     /**
212      * Returns the node with the given relative or absolute path in the file
213      * collection this node belongs to. Slash is used as directory delimiter.
214      * When the path starts with a slash, it is an absolute path and resolving
215      * is startet at the root file collection. When the path is relative,
216      * resolving starts with the current node. One dot represents the current
217      * node, Two dots represent the parent node, like in paths used by typical
218      * real filesystems.
219      * 
220      * @param path
221      *            the absolute or relative path of the node to find, may contain
222      *            . or ..
223      * @return the node at the given path, or null
224      */
225     public MCRNode getNodeByPath(String path) throws IOException {
226         MCRNode current = path.startsWith("/") ? getRoot() : this;
227         StringTokenizer st = new StringTokenizer(path, "/");
228         while (st.hasMoreTokens() && current != null) {
229             String name = st.nextToken();
230             if (name.equals(".")) {
231                 continue;
232             }
233             if (name.equals("..")) {
234                 current = current.getParent();
235             } else {
236                 current = current.getChild(name);
237             }
238         }
239         return current;
240     }
241 
242     /**
243      * Returns the content of this node for output. For a directory, it will
244      * return null.
245      * 
246      * @return the content of the file
247      */
248     public MCRContent getContent() throws IOException {
249         BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
250         return attrs.isRegularFile() ? doGetContent(attrs) : null;
251     }
252 
253     private MCRPathContent doGetContent(BasicFileAttributes attrs) throws IOException {
254         return new MCRPathContent(path, attrs);
255     }
256 
257     /**
258      * Returns the content of this node for random access read. Be sure not to
259      * write to the node using the returned object, use just for reading! For a
260      * directory, it will return null.
261      * 
262      * @return the content of this file, for random access
263      */
264     public SeekableByteChannel getRandomAccessContent() throws IOException {
265         return isFile() ? Files.newByteChannel(path, StandardOpenOption.READ) : null;
266     }
267 }