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     */
017    package org.fusesource.hawtdb.util;
018    
019    import java.io.File;
020    import java.io.FileDescriptor;
021    import java.io.FileInputStream;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.OutputStream;
026    
027    /**
028     */
029    public final class IOHelper {
030        protected static final int MAX_DIR_NAME_LENGTH;
031        protected static final int MAX_FILE_NAME_LENGTH;
032        private static final int DEFAULT_BUFFER_SIZE = 4096;
033        private IOHelper() {
034        }
035    
036        public static String getDefaultDataDirectory() {
037            return getDefaultDirectoryPrefix() + "activemq-data";
038        }
039    
040        public static String getDefaultStoreDirectory() {
041            return getDefaultDirectoryPrefix() + "amqstore";
042        }
043    
044        /**
045         * Allows a system property to be used to overload the default data
046         * directory which can be useful for forcing the test cases to use a target/
047         * prefix
048         */
049        public static String getDefaultDirectoryPrefix() {
050            try {
051                return System.getProperty("org.apache.activemq.default.directory.prefix", "");
052            } catch (Exception e) {
053                return "";
054            }
055        }
056    
057        /**
058         * Converts any string into a string that is safe to use as a file name.
059         * The result will only include ascii characters and numbers, and the "-","_", and "." characters.
060         *
061         * @param name
062         * @return
063         */
064        public static String toFileSystemDirectorySafeName(String name) {
065            return toFileSystemSafeName(name, true, MAX_DIR_NAME_LENGTH);
066        }
067        
068        public static String toFileSystemSafeName(String name) {
069            return toFileSystemSafeName(name, false, MAX_FILE_NAME_LENGTH);
070        }
071        
072        /**
073         * Converts any string into a string that is safe to use as a file name.
074         * The result will only include ascii characters and numbers, and the "-","_", and "." characters.
075         *
076         * @param name
077         * @param dirSeparators 
078         * @param maxFileLength 
079         * @return
080         */
081        public static String toFileSystemSafeName(String name,boolean dirSeparators,int maxFileLength) {
082            int size = name.length();
083            StringBuffer rc = new StringBuffer(size * 2);
084            for (int i = 0; i < size; i++) {
085                char c = name.charAt(i);
086                boolean valid = c >= 'a' && c <= 'z';
087                valid = valid || (c >= 'A' && c <= 'Z');
088                valid = valid || (c >= '0' && c <= '9');
089                valid = valid || (c == '_') || (c == '-') || (c == '.') || (c=='#')
090                        ||(dirSeparators && ( (c == '/') || (c == '\\')));
091    
092                if (valid) {
093                    rc.append(c);
094                } else {
095                    // Encode the character using hex notation
096                    rc.append('#');
097                    rc.append(HexSupport.toHexFromInt(c, true));
098                }
099            }
100            String result = rc.toString();
101            if (result.length() > maxFileLength) {
102                result = result.substring(result.length()-maxFileLength,result.length());
103            }
104            return result;
105        }
106        
107        public static boolean deleteFile(File fileToDelete) {
108            if (fileToDelete == null || !fileToDelete.exists()) {
109                return true;
110            }
111            boolean result = deleteChildren(fileToDelete);
112            result &= fileToDelete.delete();
113            return result;
114        }
115        
116        public static boolean deleteChildren(File parent) {
117            if (parent == null || !parent.exists()) {
118                return false;
119            }
120            boolean result = true;
121            if (parent.isDirectory()) {
122                File[] files = parent.listFiles();
123                if (files == null) {
124                    result = false;
125                } else {
126                    for (int i = 0; i < files.length; i++) {
127                        File file = files[i];
128                        if (file.getName().equals(".")
129                                || file.getName().equals("..")) {
130                            continue;
131                        }
132                        if (file.isDirectory()) {
133                            result &= deleteFile(file);
134                        } else {
135                            result &= file.delete();
136                        }
137                    }
138                }
139            }
140           
141            return result;
142        }
143        
144        
145        public static void moveFile(File src, File targetDirectory) throws IOException {
146            if (!src.renameTo(new File(targetDirectory, src.getName()))) {
147                throw new IOException("Failed to move " + src + " to " + targetDirectory);
148            }
149        }
150        
151        public static void copyFile(File src, File dest) throws IOException {
152            FileInputStream fileSrc = new FileInputStream(src);
153            FileOutputStream fileDest = new FileOutputStream(dest);
154            copyInputStream(fileSrc, fileDest);
155        }
156        
157        public static void copyInputStream(InputStream in, OutputStream out) throws IOException {
158            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
159            int len = in.read(buffer);
160            while (len >= 0) {
161                out.write(buffer, 0, len);
162                len = in.read(buffer);
163            }
164            in.close();
165            out.close();
166        }
167        
168        static {
169            MAX_DIR_NAME_LENGTH = Integer.valueOf(System.getProperty("MaximumDirNameLength","200")).intValue();  
170            MAX_FILE_NAME_LENGTH = Integer.valueOf(System.getProperty("MaximumFileNameLength","64")).intValue();             
171        }
172    
173        
174        public static void mkdirs(File dir) throws IOException {
175            if (dir.exists()) {
176                if (!dir.isDirectory()) {
177                    throw new IOException("Failed to create directory '" + dir +"', regular file already existed with that name");
178                }
179                
180            } else {
181                if (!dir.mkdirs()) {
182                    throw new IOException("Failed to create directory '" + dir+"'");
183                }
184            }
185        }
186        
187            public interface IOStrategy {
188                    void sync(FileDescriptor fdo) throws IOException;
189            }       
190            
191            static final IOStrategy IO_STRATEGY = createIOStrategy();
192            
193            private static IOStrategy createIOStrategy() {
194                    
195                    // On OS X, the fsync system call does not fully flush the hardware buffers.. 
196                    // to do that you have to do an fcntl call, and the only way to do that is to
197                    // do some JNI.  
198                    String os = System.getProperty("os.name");
199    //              if( "Mac OS X".equals(os) ) {
200    //
201    //                      // We will gracefully fall back to default JDK file sync behavior
202    //                      // if the JNA library is not in the path, and we can't set the 
203    //                      // FileDescriptor.fd field accessible.
204    //                      try {
205    //                              final Field field = FileDescriptor.class.getDeclaredField("fd");
206    //                              field.setAccessible(true);
207    //                              // Try to dynamically load the JNA impl of the CLibrary interface..
208    //                              final CLibrary lib = getCLibrary();
209    //                              return new IOStrategy() {
210    //                                      static final int F_FULLFSYNC = 51;      
211    //                                      public void sync(FileDescriptor fd) throws IOException {
212    //                                              try {
213    //                                                      int id = field.getInt(fd);
214    //                                                      lib.fcntl(id, F_FULLFSYNC);
215    //                                              } catch (Exception e) {
216    //                                                      throw IOExceptionSupport.create(e);
217    //                                              }
218    //                                      }
219    //                              };
220    //                      } catch (Throwable ignore) {
221    //                              // Perhaps we should issue a warning here so folks know that 
222    //                              // the disk syncs are not going to be of very good quality.
223    //                      }
224    //              }
225                    
226                    return new IOStrategy() {
227                            public void sync(FileDescriptor fd) throws IOException {
228                                    fd.sync();
229                            }
230                    };
231            }
232    
233    //      @SuppressWarnings("unchecked")
234    //      public static CLibrary getCLibrary() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
235    //              Class clazz = IOHelper.class.getClassLoader().loadClass("org.apache.activemq.util.os.JnaCLibrary");
236    //              final CLibrary lib = (CLibrary) clazz.getField("INSTANCE").get(null);
237    //              return lib;
238    //      }
239            
240            static public void sync(FileDescriptor fd) throws IOException {
241                    IO_STRATEGY.sync(fd);
242            }
243    
244    }