View Javadoc

1   package org.varienaja.util;
2   
3   import java.io.BufferedInputStream;
4   import java.io.BufferedOutputStream;
5   import java.io.File;
6   import java.io.FileInputStream;
7   import java.io.FileOutputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.io.OutputStream;
11  import java.util.zip.ZipEntry;
12  
13  import org.apache.log4j.Logger;
14  
15  /**
16   * Some much-used operations on files.
17   * TODO Add a logger, don't print stacktraces.
18   * @author Varienaja
19   */
20  public class FileOperations {
21  	private static final int BUFFER = 4096;
22  	private static final Logger LOG = Logger.getLogger(FileOperations.class);
23  	
24  	/**
25  	 * Finds a filename that is not already used. This method does not create the 
26  	 * file for you, so it is theoretically possible that the returned available
27  	 * filename is already taken.
28  	 * @param dstdir The directory where the file should be created.
29  	 * @param dst The desired filename
30  	 * @return The desider filename, if still available. Or an alternative where needed.
31  	 */
32  	public static String createUniqueFilename(String dstdir,String dst) {
33  		File file = new File(dstdir+dst);
34  		if (!file.exists()) {
35  			return dstdir+dst;
36  		} else {
37  			//Misuse of i:
38  			String ext = "";
39  			String fn = null;
40  			int i = dst.lastIndexOf(".");
41  			if (i<0) {
42  				fn = dst;
43  			} else {
44  				fn = dst.substring(0,i);
45  				ext = dst.substring(i);
46  			}
47  			String uniqueName = "";
48  			i=0;
49  			while (file.exists()) {
50  				i++;
51  				uniqueName = dstdir+fn+"("+i+")"+ext;
52  				LOG.debug("Clash detected, trying: "+uniqueName);
53  				file = new File(uniqueName);
54  			}
55  			LOG.debug("Unique filename: "+uniqueName);
56  			return uniqueName;
57  		}
58  	}
59  	
60  	/**
61  	 * Copies a file. If the destination-directory contains illegal characters, they
62  	 * will be replaced by underscores. If the filename contains illegal characters,
63  	 * they will be replaced by underscores. If the target file already exists, a new
64  	 * filename will be generated.
65  	 * @param src The location of the sourcefile.
66  	 * @param dstdir The target directory.
67  	 * @param dst The requested filename (without directory-part).
68  	 * @return The filename, that the file was finally copied to.
69  	 * @throws IOException
70  	 */
71  	public static String copyFile(String src,String dstdir,String dst) throws IOException {
72  		dstdir = translateIllegalDirectoryChars(dstdir);
73  		if (dstdir.charAt(dstdir.length()-1)!=File.separatorChar) {
74  			dstdir = dstdir + File.separator;
75  		}
76  		dst = translateIllegalFileChars(dst);
77  		try {
78  			if (!new File(dstdir).mkdirs()) {
79  				throw new IOException("Failed to create directory: "+dstdir);
80  			}
81  			String result = createUniqueFilename(dstdir,dst);
82  			
83  			FileInputStream source = new FileInputStream(src);
84  			FileOutputStream dest = new FileOutputStream(result);
85  	
86  			copyStream(source,dest);
87  			dest.close();
88  			source.close();
89  			return result;
90  		} catch (IOException e) {
91  			LOG.error("Error copying file: "+e);
92  			throw e;
93  		}
94  	}
95  	
96  	/**
97  	 * Moves a file. Behaves just like copyFile ( @see copyFile ).
98  	 * @param src
99  	 * @param dstdir
100 	 * @param dst
101 	 * @return
102 	 * @throws IOException
103 	 */
104 	public static String moveFile(String src,String dstdir,String dst) throws IOException {
105 		dstdir = translateIllegalDirectoryChars(dstdir);
106 		if (dstdir.charAt(dstdir.length()-1)!=File.separatorChar) {
107 			dstdir = dstdir + File.separator;
108 		}
109 		dst = translateIllegalFileChars(dst);
110 		File destDir = new File(dstdir);
111 		if(!destDir.exists()) {
112 			if (!destDir.mkdirs()) {
113 				throw new IOException("Failed to create directory: "+dstdir);
114 			}
115 		}
116 		String result = createUniqueFilename(dstdir,dst);
117 		
118 		File source = new File (src);
119 		if (source.renameTo(new File(result))) {
120 			return result;
121 		} else {
122 			throw new IOException("Error moving from: "+src+" to "+result);
123 		}
124 	}
125 	
126 	public static void copyStream(InputStream in, OutputStream out) {
127 		int count;
128 		byte data[] = new byte[BUFFER];
129 		
130 		try {
131 			BufferedInputStream source = new BufferedInputStream(in);
132 			BufferedOutputStream dest = new BufferedOutputStream(out,BUFFER);
133 			while ((count = source.read(data,0,BUFFER))!=-1) {
134 				dest.write(data,0,count);
135 			}
136 			dest.flush();
137 		} catch (IOException e) {
138 			LOG.error("Error copying stream: "+e);
139 		}
140 	}
141 	
142 	/**
143 	 * Creates a file in the temp-directory and returns the filename
144 	 * @param in The InputStream
145 	 * @param tmpname The filename to use (without path)
146 	 * @return The fully qualified filename
147 	 * @throws IOException
148 	 */
149 	public static String tmpFile(InputStream in,String tmpname) throws IOException {
150 		File outFile = File.createTempFile(tmpname, null);
151 		return streamToFile(in,outFile);
152 	}
153 
154 	/**
155 	 * Saves an InputStream to a file. If the file does not exist, it
156 	 * is created. If the file already exists, it is overwritten!
157 	 * @param in The InputStream
158 	 * @param filename The file to save the Stream into.
159 	 * @return The filename
160 	 * @throws IOException
161 	 */
162 	public static String streamToFile(InputStream in,String filename) throws IOException {
163 		FileOutputStream out = new FileOutputStream(filename);
164 		copyStream(in,out);
165 		out.close();
166 		return filename;
167 	}
168 
169 	/**
170 	 * Saves an InputStream to a file. If the file does not exist, it
171 	 * is created. If the file already exists, it is overwritten!
172 	 * @param in The InputStream
173 	 * @param filename The file to save the Stream into.
174 	 * @return The file
175 	 * @throws IOException
176 	 */
177 	public static String streamToFile(InputStream in,File file) throws IOException {
178 		FileOutputStream out = new FileOutputStream(file);
179 		copyStream(in,out);
180 		out.close();
181 		return file.getAbsolutePath();
182 	}
183 
184 	/**
185 	 * Deletes a file from the filesystem.
186 	 * @param filename The file to delete
187 	 * @return True, if succesfull; False otherwise.
188 	 */
189 	public static boolean deleteFile(String filename) {
190 		File file = new File(filename);
191 		return file.delete();
192 	}
193 	/**
194 	 * Deletes a directory including all contents.
195 	 * <p><em>WARNING</em>: This method will happily delete the contents of your entiry
196 	 * hard drive. Use with <em>extreme care</em>.
197 	 *  
198 	 * @param dir The directory to delete.
199 	 * @return True if the directory was deleted succesfully, false otherwise.
200 	 */
201 	public static boolean deleteDir(File dir) {
202 		 if (dir == null) {
203 			 return false;
204 		 }
205 
206 		// to see if this directory is actually a symbolic link to a
207 		// directory,
208 		// we want to get its canonical path - that is, we follow the link
209 		// to
210 		// the file it's actually linked to
211 		File candir;
212 		try {
213 			candir = dir.getCanonicalFile();
214 		} catch (IOException e) {
215 			return false;
216 		}
217 
218 		// a symbolic link has a different canonical path than its actual
219 		// path,
220 		// unless it's a link to itself
221 		if (!candir.equals(dir.getAbsoluteFile())) {
222 			// this file is a symbolic link, and there's no reason for us to
223 			// follow it, because then we might be deleting something
224 			// outside of
225 			// the directory we were told to delete
226 			return false;
227 		}
228 
229 		// now we go through all of the files and subdirectories in the
230 		// directory and delete them one by one
231 		File[] files = candir.listFiles();
232 		if (files != null) {
233 			for (int i = 0; i < files.length; i++) {
234 				File file = files[i];
235 
236 				// in case this directory is actually a symbolic link, or
237 				// it's
238 				// empty, we want to try to delete the link before we try
239 				// anything
240 				boolean deleted = file.delete();
241 				if (!deleted) {
242 					// deleting the file failed, so maybe it's a non-empty
243 					// directory
244 					if (file.isDirectory())
245 						deleteDir(file);
246 
247 					// otherwise, there's nothing else we can do
248 				}
249 			}
250 		}
251         // now that we tried to clear the directory out, we can try to delete it
252         // again
253         return dir.delete();  
254 	}
255 
256 	/**
257 	 * Returns the "from" string with all / \ : * ? " < > |
258 	 * characters replaced with "_". In addition, points (.) and spaces
259 	 * are not allowed as first or last character of the filename.
260 	 * @param from The source string
261 	 * @return The source string with every occurence of / \ : * ? " < > |
262 	 *         replaced with "_".
263 	 */
264 	public static String translateIllegalFileChars(String from) {
265 		//Forbidden characters:	/ \ : * ? " < > |
266 		 if (from==null) return null;
267 		 return from.replaceAll("\"|<|>|\\||:|\\*|\\?|/|\\\\|^\\.|\\.$|^ | $","_");
268 	}
269 	
270 	/**
271 	 * Returns the "from" string with all : * ? " < > |
272 	 * characters replaced with "_". In addition, points (.) and spaces
273 	 * are not allowed as first or last character of the directory name.
274 	 * @param from The source string
275 	 * @return The source string with every occurence of : * ? " < > |
276 	 *         replaced with "_".
277 	 */
278 	public static String translateIllegalDirectoryChars(String from) {
279 		//Forbidden characters:	: * ? " < > |
280 		 if (from==null) return null;
281 		 return from.replaceAll("\"|<|>|\\||\\*|\\?|^\\.|\\.$|^ | $","_");
282 	}
283 	
284 	/**
285 	 * Takes a zip-inputstream and extracts the contents to the specified directory.
286 	 * If files in the zipstream are already present at the specifiek location, a new
287 	 * name for the file will be created using the createUniqueFilename-method.
288 	 * @param zis The Zip-inputstream
289 	 * @param directory The location to unzip to
290 	 * @return The number of files (excluding directories) exracted
291 	 * @see createUniqueFilename
292 	 */
293 	public static int extractToDirectory(TolerantZipInputStream zis, String directory) {
294 		int unpackedFiles = 0;
295 		
296 		ZipEntry entry;
297 		try {
298 			while ((entry = zis.getNextEntry()) != null) {
299 				if (entry.isDirectory()) { //Directory-entry
300 					String destFN = directory + File.separator + entry.getName();
301 					LOG.debug("Creating directory: "+destFN);
302 					if (!new File(destFN).mkdirs()) {
303 						throw new IOException("Failed to create directoy: "+destFN);
304 					}
305 				} else { //File-entry
306 					String destFN = createUniqueFilename(directory + File.separator,entry.getName());
307 					LOG.debug("Extracting to: "+destFN);
308 					streamToFile(zis, destFN);
309 					unpackedFiles++;
310 				}
311 			}
312 			zis.close();
313 		} catch (IllegalArgumentException e) {
314 			e.printStackTrace();
315 			LOG.error("Error reading zipfile, does it contain non-ascii characters? " +e);
316 			/* TODO Java doesn't seem to like zipfiles containing entries with non-ascii
317 			 * characters in their filename. (&euml;, for instance) There probably is a
318 			 * workaround to be found for this behaviour. 
319 			 * 
320 			 * The workaround is to treat the input as ISO-8859-1, and convert that to UTF8
321 			 */
322 		} catch (IOException e) {
323 			LOG.error(e);
324 		}
325 		
326 		return unpackedFiles;
327 	}
328 	
329 }