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.pageStore.disk;
018
019import java.io.File;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.Set;
023
024import org.apache.wicket.util.file.Files;
025import org.apache.wicket.util.lang.Args;
026
027/**
028 * Keep files in a nested filed structure to minimize amount of directory entries (inodes) in a single directory. 
029 * 
030 * @author svenmeier
031 */
032public class NestedFolders
033{
034        private final File base;
035        
036        /**
037         * Create folders in the given base folder.
038         * 
039         * @param base base has to be a folder
040         */
041        public NestedFolders(File base)
042        {
043                this.base = Args.notNull(base, "base");
044        }
045        
046        public File getBase()
047        {
048                return base;
049        }
050        
051        /**
052         * Get a nested folder for the given name.
053         * 
054         * @param name name 
055         * @param create
056         * @return
057         */
058        public File get(String name, final boolean create)
059        {
060                name = name.replace('*', '_');
061                name = name.replace('/', '_');
062                name = name.replace(':', '_');
063
064                String path = createPathFrom(name);
065
066                File folder = new File(base, path);
067                if (create && folder.exists() == false)
068                {
069                        Files.mkdirs(folder);
070                }
071                return folder;
072        }
073
074        private String createPathFrom(final String name)
075        {
076                int hash = Math.abs(name.hashCode());
077                String low = String.valueOf(hash % 9973);
078                String high = String.valueOf((hash / 9973) % 9973);
079                StringBuilder bs = new StringBuilder(name.length() + 10);
080                bs.append(low);
081                bs.append(File.separator);
082                bs.append(high);
083                bs.append(File.separator);
084                bs.append(name);
085
086                return bs.toString();
087        }
088
089        /**
090         * Remove a nested folder.
091         * 
092         * @param name name of folder
093         */
094        public void remove(String name)
095        {
096                File folder = get(name, false);
097                if (folder.exists())
098                {
099                        Files.removeFolder(folder);
100                        
101                        File high = folder.getParentFile();
102                        final String[] highChildren = high.list();
103                        if (highChildren != null && highChildren.length == 0 && Files.removeFolder(high))
104                        {
105                                File low = high.getParentFile();
106                                final String[] lowChildren = low.list();
107                                if (lowChildren != null && lowChildren.length == 0)
108                                {
109                                        Files.removeFolder(low);
110                                }
111                        }
112                }
113        }
114
115        /**
116         * Get all files inside.
117         * 
118         * @return files
119         */
120        public Set<File> getAll()
121        {
122                Set<File> files = new HashSet<>();
123                
124                if (base.exists())
125                {
126                        for (File low : Files.list(base))
127                        {
128                                for (File high: Files.list(low))
129                                {
130                                        Collections.addAll(files, Files.list(high));
131                                }
132                        }
133                }
134                
135                return files;
136        }
137}