001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.util.file;
018
019import java.io.IOException;
020import java.net.URI;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.List;
024
025import org.apache.wicket.util.lang.Bytes;
026
027/**
028 * This folder subclass provides some type safety and extensibility for "files" that hold other
029 * files.
030 * 
031 * @author Jonathan Locke
032 */
033public class Folder extends File
034{
035        /**
036         * Filter for files
037         * 
038         * @author Jonathan Locke
039         */
040        public static interface FileFilter
041        {
042                /**
043                 * File filter that matches all files
044                 */
045                public static FileFilter ALL_FILES = new FileFilter()
046                {
047                        @Override
048                        public boolean accept(final File file)
049                        {
050                                return true;
051                        }
052                };
053
054                /**
055                 * @param file
056                 *            The file to test
057                 * @return True if the file should be accepted
058                 */
059                public boolean accept(File file);
060        }
061
062        /**
063         * Filter for folders
064         * 
065         * @author Jonathan Locke
066         */
067        public static interface FolderFilter
068        {
069                /**
070                 * @param folder
071                 *            The folder to test
072                 * @return True if the file should be accepted
073                 */
074                public boolean accept(Folder folder);
075        }
076
077        private static final long serialVersionUID = 1L;
078
079        /**
080         * Constructor.
081         * 
082         * @param parent
083         *            parent
084         * @param child
085         *            child
086         */
087        public Folder(final Folder parent, final String child)
088        {
089                super(parent, child);
090        }
091
092        /**
093         * Construct.
094         * 
095         * @param file
096         *            File
097         */
098        public Folder(final java.io.File file)
099        {
100                this(file.getPath());
101        }
102
103        /**
104         * Constructor.
105         * 
106         * @param pathname
107         *            path name
108         */
109        public Folder(final String pathname)
110        {
111                super(pathname);
112        }
113
114        /**
115         * Constructor.
116         * 
117         * @param parent
118         *            parent
119         * @param child
120         *            child
121         */
122        public Folder(final String parent, final String child)
123        {
124                super(parent, child);
125        }
126
127        /**
128         * Constructor.
129         * 
130         * @param uri
131         *            folder uri
132         */
133        public Folder(final URI uri)
134        {
135                super(uri);
136        }
137
138        /**
139         * Does a mkdirs() on this folder if it does not exist. If the folder cannot be created, an
140         * IOException is thrown.
141         * 
142         * @throws IOException
143         *             Thrown if folder cannot be created
144         */
145        public void ensureExists() throws IOException
146        {
147                if (!exists() && !mkdirs())
148                {
149                        throw new IOException("Unable to create folder " + this);
150                }
151        }
152
153        /**
154         * @param name
155         *            Name of child folder
156         * @return Child file object
157         */
158        public Folder folder(final String name)
159        {
160                return new Folder(this, name);
161        }
162
163        /**
164         * @return Disk space free on the partition where this folder lives
165         */
166        public Bytes freeDiskSpace()
167        {
168                return Bytes.bytes(super.getFreeSpace());
169        }
170
171        /**
172         * @return Files in this folder
173         */
174        public File[] getFiles()
175        {
176                return getFiles(FileFilter.ALL_FILES);
177        }
178
179        /**
180         * @return All files nested within this folder
181         */
182        public File[] getNestedFiles()
183        {
184                return getNestedFiles(FileFilter.ALL_FILES);
185        }
186
187        /**
188         * Gets files in this folder matching a given filter recursively.
189         * 
190         * @param filter
191         *            The filter
192         * @return The list of files
193         */
194        public File[] getNestedFiles(final FileFilter filter)
195        {
196                final List<File> files = new ArrayList<>();
197                files.addAll(Arrays.asList(getFiles(filter)));
198                final Folder[] folders = getFolders();
199                for (Folder folder : folders)
200                {
201                        files.addAll(Arrays.asList(folder.getNestedFiles(filter)));
202                }
203                return files.toArray(new File[files.size()]);
204        }
205
206        /**
207         * @param filter
208         *            File filter
209         * @return Files
210         */
211        public File[] getFiles(final FileFilter filter)
212        {
213                // Get list of java.io files
214                final java.io.File[] files = listFiles(new java.io.FileFilter()
215                {
216                        /**
217                         * @see java.io.FileFilter#accept(java.io.File)
218                         */
219                        @Override
220                        public boolean accept(final java.io.File file)
221                        {
222                                return file.isFile() && filter.accept(new File(file));
223                        }
224                });
225
226                // Convert java.io files to org.apache.wicket files
227                if (files != null)
228                {
229                        final File[] wicketFiles = new File[files.length];
230                        for (int i = 0; i < files.length; i++)
231                        {
232                                wicketFiles[i] = new File(files[i]);
233                        }
234                        return wicketFiles;
235                }
236                return new File[0];
237        }
238
239        /**
240         * Gets all folders in this folder, except "." and ".."
241         * 
242         * @return Folders
243         */
244        public Folder[] getFolders()
245        {
246                return getFolders(new FolderFilter()
247                {
248                        @Override
249                        public boolean accept(final Folder folder)
250                        {
251                                final String name = folder.getName();
252                                return !name.equals(".") && !name.equals("..");
253                        }
254                });
255        }
256
257        /**
258         * @param filter
259         *            Folder filter
260         * @return Folders
261         */
262        public Folder[] getFolders(final FolderFilter filter)
263        {
264                // Get java io files that are directories matching the filter
265                final java.io.File[] files = listFiles(new java.io.FileFilter()
266                {
267                        /**
268                         * @see java.io.FileFilter#accept(java.io.File)
269                         */
270                        @Override
271                        public boolean accept(final java.io.File file)
272                        {
273                                return file.isDirectory() && filter.accept(new Folder(file.getPath()));
274                        }
275                });
276
277                // Convert
278                if (files != null)
279                {
280                        final Folder[] wicketFolders = new Folder[files.length];
281                        for (int i = 0; i < files.length; i++)
282                        {
283                                wicketFolders[i] = new Folder(files[i]);
284                        }
285                        return wicketFolders;
286                }
287                return new Folder[0];
288        }
289
290        /**
291         * Removes this folder and everything in it, recursively. A best effort is made to remove nested
292         * folders and files in depth-first order.
293         * 
294         * @return True if the folder was successfully removed
295         */
296        @Override
297        public boolean remove()
298        {
299                return remove(this);
300        }
301
302        /**
303         * Removes all the files in this folder.
304         * 
305         * @return True if any files were successfully removed
306         */
307        public boolean removeFiles()
308        {
309                final File[] files = getFiles();
310                boolean success = true;
311                for (File file : files)
312                {
313                        success = file.remove() && success;
314                }
315                return success;
316        }
317
318        /**
319         * Removes everything in the given folder and then the folder itself.
320         * 
321         * @param folder
322         *            The folder
323         * @return True if the folder was successfully removed
324         */
325        private boolean remove(final Folder folder)
326        {
327                final Folder[] folders = getFolders();
328                boolean success = true;
329                for (Folder subfolder : folders)
330                {
331                        success = subfolder.remove() && success;
332                }
333                success = removeFiles() && success;
334                return folder.delete() && success;
335        }
336}