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.activemq.util;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.FilenameFilter;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.nio.file.StandardCopyOption;
029import java.util.ArrayList;
030import java.util.List;
031import java.util.Stack;
032
033/**
034 * Collection of File and Folder utility methods.
035 */
036public final class IOHelper {
037
038    protected static final int MAX_DIR_NAME_LENGTH;
039    protected static final int MAX_FILE_NAME_LENGTH;
040    private static final int DEFAULT_BUFFER_SIZE = 4096;
041
042    private IOHelper() {
043    }
044
045    public static String getDefaultDataDirectory() {
046        return getDefaultDirectoryPrefix() + "activemq-data";
047    }
048
049    public static String getDefaultStoreDirectory() {
050        return getDefaultDirectoryPrefix() + "amqstore";
051    }
052
053    /**
054     * Allows a system property to be used to overload the default data
055     * directory which can be useful for forcing the test cases to use a target/
056     * prefix
057     */
058    public static String getDefaultDirectoryPrefix() {
059        try {
060            return System.getProperty("org.apache.activemq.default.directory.prefix", "");
061        } catch (Exception e) {
062            return "";
063        }
064    }
065
066    /**
067     * Converts any string into a string that is safe to use as a file name. The
068     * result will only include ascii characters and numbers, and the "-","_",
069     * and "." characters.
070     *
071     * @param name
072     * @return safe name of the directory
073     */
074    public static String toFileSystemDirectorySafeName(String name) {
075        return toFileSystemSafeName(name, true, MAX_DIR_NAME_LENGTH);
076    }
077
078    public static String toFileSystemSafeName(String name) {
079        return toFileSystemSafeName(name, false, MAX_FILE_NAME_LENGTH);
080    }
081
082    /**
083     * Converts any string into a string that is safe to use as a file name. The
084     * result will only include ascii characters and numbers, and the "-","_",
085     * and "." characters.
086     *
087     * @param name
088     * @param dirSeparators
089     * @param maxFileLength
090     * @return file system safe name
091     */
092    public static String toFileSystemSafeName(String name, boolean dirSeparators, int maxFileLength) {
093        int size = name.length();
094        StringBuffer rc = new StringBuffer(size * 2);
095        for (int i = 0; i < size; i++) {
096            char c = name.charAt(i);
097            boolean valid = c >= 'a' && c <= 'z';
098            valid = valid || (c >= 'A' && c <= 'Z');
099            valid = valid || (c >= '0' && c <= '9');
100            valid = valid || (c == '_') || (c == '-') || (c == '.') || (c == '#') || (dirSeparators && ((c == '/') || (c == '\\')));
101
102            if (valid) {
103                rc.append(c);
104            } else {
105                // Encode the character using hex notation
106                rc.append('#');
107                rc.append(HexSupport.toHexFromInt(c, true));
108            }
109        }
110        String result = rc.toString();
111        if (result.length() > maxFileLength) {
112            result = result.substring(result.length() - maxFileLength, result.length());
113        }
114        return result;
115    }
116
117    public static boolean delete(File top) {
118        boolean result = true;
119        Stack<File> files = new Stack<File>();
120        // Add file to the stack to be processed...
121        files.push(top);
122        // Process all files until none remain...
123        while (!files.isEmpty()) {
124            File file = files.pop();
125            if (file.isDirectory()) {
126                File list[] = file.listFiles();
127                if (list == null || list.length == 0) {
128                    // The current directory contains no entries...
129                    // delete directory and continue...
130                    result &= file.delete();
131                } else {
132                    // Add back the directory since it is not empty....
133                    // and when we process it again it will be empty and can be
134                    // deleted safely...
135                    files.push(file);
136                    for (File dirFile : list) {
137                        if (dirFile.isDirectory()) {
138                            // Place the directory on the stack...
139                            files.push(dirFile);
140                        } else {
141                            // This is a simple file, delete it...
142                            result &= dirFile.delete();
143                        }
144                    }
145                }
146            } else {
147                // This is a simple file, delete it...
148                result &= file.delete();
149            }
150        }
151        return result;
152    }
153
154    public static boolean deleteFile(File fileToDelete) {
155        if (fileToDelete == null || !fileToDelete.exists()) {
156            return true;
157        }
158        boolean result = deleteChildren(fileToDelete);
159        result &= fileToDelete.delete();
160        return result;
161    }
162
163    public static boolean deleteChildren(File parent) {
164        if (parent == null || !parent.exists()) {
165            return false;
166        }
167        boolean result = true;
168        if (parent.isDirectory()) {
169            File[] files = parent.listFiles();
170            if (files == null) {
171                result = false;
172            } else {
173                for (int i = 0; i < files.length; i++) {
174                    File file = files[i];
175                    if (file.getName().equals(".") || file.getName().equals("..")) {
176                        continue;
177                    }
178                    if (file.isDirectory()) {
179                        result &= deleteFile(file);
180                    } else {
181                        result &= file.delete();
182                    }
183                }
184            }
185        }
186
187        return result;
188    }
189
190    public static void moveFile(File src, File targetDirectory) throws IOException {
191        if (!src.renameTo(new File(targetDirectory, src.getName()))) {
192
193            // If rename fails we must do a true deep copy instead.
194            Path sourcePath = src.toPath();
195            Path targetDirPath = targetDirectory.toPath();
196
197            try {
198                Files.move(sourcePath, targetDirPath.resolve(sourcePath.getFileName()), StandardCopyOption.REPLACE_EXISTING);
199            } catch (IOException ex) {
200                throw new IOException("Failed to move " + src + " to " + targetDirectory + " - " + ex.getMessage());
201            }
202        }
203    }
204
205    public static void moveFiles(File srcDirectory, File targetDirectory, FilenameFilter filter) throws IOException {
206        if (!srcDirectory.isDirectory()) {
207            throw new IOException("source is not a directory");
208        }
209
210        if (targetDirectory.exists() && !targetDirectory.isDirectory()) {
211            throw new IOException("target exists and is not a directory");
212        } else {
213            mkdirs(targetDirectory);
214        }
215
216        List<File> filesToMove = new ArrayList<File>();
217        getFiles(srcDirectory, filesToMove, filter);
218
219        for (File file : filesToMove) {
220            if (!file.isDirectory()) {
221                moveFile(file, targetDirectory);
222            }
223        }
224    }
225
226    public static void copyFile(File src, File dest) throws IOException {
227        copyFile(src, dest, null);
228    }
229
230    public static void copyFile(File src, File dest, FilenameFilter filter) throws IOException {
231        if (src.getCanonicalPath().equals(dest.getCanonicalPath()) == false) {
232            if (src.isDirectory()) {
233
234                mkdirs(dest);
235                List<File> list = getFiles(src, filter);
236                for (File f : list) {
237                    if (f.isFile()) {
238                        File target = new File(getCopyParent(src, dest, f), f.getName());
239                        copySingleFile(f, target);
240                    }
241                }
242
243            } else if (dest.isDirectory()) {
244                mkdirs(dest);
245                File target = new File(dest, src.getName());
246                copySingleFile(src, target);
247            } else {
248                copySingleFile(src, dest);
249            }
250        }
251    }
252
253    static File getCopyParent(File from, File to, File src) {
254        File result = null;
255        File parent = src.getParentFile();
256        String fromPath = from.getAbsolutePath();
257        if (parent.getAbsolutePath().equals(fromPath)) {
258            // one level down
259            result = to;
260        } else {
261            String parentPath = parent.getAbsolutePath();
262            String path = parentPath.substring(fromPath.length());
263            result = new File(to.getAbsolutePath() + File.separator + path);
264        }
265        return result;
266    }
267
268    static List<File> getFiles(File dir, FilenameFilter filter) {
269        List<File> result = new ArrayList<File>();
270        getFiles(dir, result, filter);
271        return result;
272    }
273
274    static void getFiles(File dir, List<File> list, FilenameFilter filter) {
275        if (!list.contains(dir)) {
276            list.add(dir);
277            String[] fileNames = dir.list(filter);
278            for (int i = 0; i < fileNames.length; i++) {
279                File f = new File(dir, fileNames[i]);
280                if (f.isFile()) {
281                    list.add(f);
282                } else {
283                    getFiles(dir, list, filter);
284                }
285            }
286        }
287    }
288
289    public static void copySingleFile(File src, File dest) throws IOException {
290        FileInputStream fileSrc = new FileInputStream(src);
291        FileOutputStream fileDest = new FileOutputStream(dest);
292        copyInputStream(fileSrc, fileDest);
293    }
294
295    public static void copyInputStream(InputStream in, OutputStream out) throws IOException {
296        try {
297            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
298            int len = in.read(buffer);
299            while (len >= 0) {
300                out.write(buffer, 0, len);
301                len = in.read(buffer);
302            }
303        } finally {
304            in.close();
305            out.close();
306        }
307    }
308
309    static {
310        MAX_DIR_NAME_LENGTH = Integer.getInteger("MaximumDirNameLength", 200);
311        MAX_FILE_NAME_LENGTH = Integer.getInteger("MaximumFileNameLength", 64);
312    }
313
314    public static int getMaxDirNameLength() {
315        return MAX_DIR_NAME_LENGTH;
316    }
317
318    public static int getMaxFileNameLength() {
319        return MAX_FILE_NAME_LENGTH;
320    }
321
322    public static void mkdirs(File dir) throws IOException {
323        if (dir.exists()) {
324            if (!dir.isDirectory()) {
325                throw new IOException("Failed to create directory '" + dir +
326                                      "', regular file already existed with that name");
327            }
328
329        } else {
330            if (!dir.mkdirs()) {
331                throw new IOException("Failed to create directory '" + dir + "'");
332            }
333        }
334    }
335}