001    /*
002     * $Revision: 15006 $ 
003     * $Date: 2009-03-25 10:28:39 +0100 (Wed, 25 Mar 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.datamodel.ifs2;
025    
026    import java.util.ArrayList;
027    import java.util.Date;
028    import java.util.List;
029    import java.util.StringTokenizer;
030    
031    import org.apache.commons.vfs.FileContent;
032    import org.apache.commons.vfs.FileObject;
033    import org.apache.commons.vfs.FileType;
034    import org.apache.commons.vfs.RandomAccessContent;
035    import org.apache.commons.vfs.VFS;
036    import org.apache.commons.vfs.util.RandomAccessMode;
037    
038    /**
039     * Represents a file, directory or file collection within a file store. Files
040     * and directories can be either really stored, or virtually existing as a child
041     * node contained within a stored container file like zip or tar.
042     * 
043     * @author Frank Lützenkirchen
044     */
045    public abstract class MCRNode {
046        /**
047         * The file object representing this node in the underlying filesystem.
048         */
049        protected FileObject fo;
050    
051        /**
052         * The parent node owning this file, a directory or container file
053         */
054        protected MCRNode parent;
055    
056        /**
057         * Creates a new node representing a child of the given parent
058         * 
059         * @param parent
060         *            the parent node
061         * @param fo
062         *            the file object representing this node in the underlying
063         *            filesystem
064         */
065        protected MCRNode(MCRNode parent, FileObject fo) {
066            this.fo = fo;
067            this.parent = parent;
068        }
069    
070        /**
071         * Returns the file or directory name
072         * 
073         * @return the node's filename
074         */
075        public String getName() {
076            return fo.getName().getBaseName();
077        }
078    
079        /**
080         * Returns the complete path of this node up to the root file collection.
081         * Path always start with a slash, slash is used as directory delimiter.
082         * 
083         * @return the absolute path of this node
084         * @throws Exception
085         */
086        public String getPath() throws Exception {
087            if (parent != null)
088                if (parent.parent == null)
089                    return "/" + getName();
090                else
091                    return parent.getPath() + "/" + getName();
092            else
093                return "/";
094        }
095    
096        /**
097         * Returns the parent node containing this node
098         * 
099         * @return the parent directory or container file
100         */
101        public MCRNode getParent() {
102            return parent;
103        }
104    
105        /**
106         * Returns the root file collection this node belongs to
107         * 
108         * @return the root file collection
109         */
110        public MCRFileCollection getRoot() {
111            return parent.getRoot();
112        }
113    
114        /**
115         * Returns true if this node is a file
116         * 
117         * @return true if this node is a file
118         */
119        public boolean isFile() throws Exception {
120            return fo.getType().equals(FileType.FILE);
121        }
122    
123        /**
124         * Returns true if this node is a directory
125         * 
126         * @return true if this node is a directory
127         */
128        public boolean isDirectory() throws Exception {
129            return fo.getType().equals(FileType.FOLDER);
130        }
131    
132        /**
133         * For file nodes, returns the file content size in bytes, otherwise returns
134         * 0.
135         * 
136         * @return the file size in bytes
137         */
138        public long getSize() throws Exception {
139            if (isFile())
140                return fo.getContent().getSize();
141            else
142                return 0;
143        }
144    
145        /**
146         * Returns the time this node was last modified, or null if no such time is
147         * defined in the underlying filesystem
148         * 
149         * @return the time this node was last modified
150         */
151        public Date getLastModified() throws Exception {
152            FileContent content = fo.getContent();
153            if (content != null)
154                return new Date(content.getLastModifiedTime());
155            else
156                return null;
157        }
158    
159        /**
160         * Returns true if this node has child nodes. Directories and container
161         * files like zip or tar may have child nodes.
162         * 
163         * @return true if children exist
164         */
165        public boolean hasChildren() throws Exception {
166            return getNumChildren() > 0;
167        }
168    
169        /**
170         * Returns the FileObject that is the father of all logical children of this
171         * FileObject. This may not be the current node itself, in case the node is
172         * a container file, because then intermediate FileObject instances are
173         * created by Apache VFS.
174         * 
175         * @return the father of this node's children in VFS
176         */
177        private FileObject getFather() throws Exception {
178            if (isDirectory())
179                return fo;
180            else if (getSize() == 0)
181                return null;
182            else if (VFS.getManager().canCreateFileSystem(fo)) {
183                FileObject father = fo;
184                while (VFS.getManager().canCreateFileSystem(father))
185                    father = VFS.getManager().createFileSystem(father);
186                return father;
187            } else
188                return null;
189        }
190    
191        /**
192         * Returns the number of child nodes of this node.
193         * 
194         * @return the number of child nodes of this node.
195         */
196        public int getNumChildren() throws Exception {
197            FileObject father = getFather();
198            if (father == null)
199                return 0;
200            else
201                return father.getChildren().length;
202        }
203    
204        /**
205         * Returns the children of this node. Directories and container files like
206         * zip or tar may have child nodes.
207         * 
208         * @return a List of child nodes, which may be empty, in undefined order
209         */
210        public List<MCRNode> getChildren() throws Exception {
211            List<MCRNode> children = new ArrayList<MCRNode>();
212            FileObject father = getFather();
213            if (father != null) {
214                FileObject[] childFos = father.getChildren();
215                for (int i = 0; i < childFos.length; i++) {
216                    String name = childFos[i].getName().getBaseName();
217                    MCRNode child = getChild(name);
218                    if (child != null)
219                        children.add(child);
220                }
221            }
222            return children;
223        }
224    
225        /**
226         * Creates a node instance for the given FileObject, which represents the
227         * child
228         * 
229         * @param fo
230         *            the FileObject representing the child in the underlying
231         *            filesystem
232         * @return the child node
233         */
234        protected abstract MCRNode buildChildNode(FileObject fo) throws Exception;
235    
236        /**
237         * Returns the child node with the given filename, or null
238         * 
239         * @param name
240         *            the name of the child node
241         * @return the child node with that name, or null when no such file exists
242         */
243        public MCRNode getChild(String name) throws Exception {
244            FileObject father = getFather();
245            return (father == null ? null : buildChildNode(getFather().getChild(name)));
246        }
247    
248        /**
249         * Returns the node with the given relative or absolute path in the file
250         * collection this node belongs to. Slash is used as directory delimiter.
251         * When the path starts with a slash, it is an absolute path and resolving
252         * is startet at the root file collection. When the path is relative,
253         * resolving starts with the current node. One dot represents the current
254         * node, Two dots represent the parent node, like in paths used by typical
255         * real filesystems.
256         * 
257         * @param path
258         *            the absolute or relative path of the node to find, may contain
259         *            . or ..
260         * @return the node at the given path, or null
261         */
262        public MCRNode getNodeByPath(String path) throws Exception {
263            MCRNode current = path.startsWith("/") ? getRoot() : this;
264            StringTokenizer st = new StringTokenizer(path, "/");
265            while (st.hasMoreTokens() && (current != null)) {
266                String name = st.nextToken();
267                if (name.equals("."))
268                    continue;
269                else if (name.equals(".."))
270                    current = current.getParent();
271                else
272                    current = current.getChild(name);
273            }
274            return current;
275        }
276    
277        /**
278         * Returns the content of this node for output. For a directory, it will
279         * return null.
280         * 
281         * @return the content of the file
282         */
283        public MCRContent getContent() throws Exception {
284            return (isFile() ? MCRContent.readFrom(fo) : null);
285        }
286    
287        /**
288         * Returns the content of this node for random access read. Be sure not to
289         * write to the node using the returned object, use just for reading! For a
290         * directory, it will return null.
291         * 
292         * @return the content of this file, for random access
293         */
294        public RandomAccessContent getRandomAccessContent() throws Exception {
295            return (isFile() ? fo.getContent().getRandomAccessContent(RandomAccessMode.READ) : null);
296        }
297    }