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 }