View Javadoc

1   package net.sf.jpkgmk.util;
2   
3   import java.io.BufferedInputStream;
4   import java.io.BufferedOutputStream;
5   import java.io.File;
6   import java.io.FileFilter;
7   import java.io.FileInputStream;
8   import java.io.FileOutputStream;
9   import java.io.IOException;
10  import java.io.InputStream;
11  import java.io.InputStreamReader;
12  import java.io.OutputStream;
13  import java.io.OutputStreamWriter;
14  import java.io.Reader;
15  import java.io.Writer;
16  import java.net.URL;
17  import java.nio.channels.FileChannel;
18  import java.nio.charset.Charset;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.zip.GZIPOutputStream;
24  
25  import net.sf.jpkgmk.PackageException;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.tools.tar.TarEntry;
30  import org.apache.tools.tar.TarOutputStream;
31  
32  /**
33   * @author gommma (gommma AT users.sourceforge.net)
34   * @author Last changed by: $Author: gommma $
35   * @version $Revision: 2 $ $Date: 2008-08-20 21:14:19 +0200 (Mi, 20 Aug 2008) $
36   * @since 1.0
37   */
38  public class FileUtil 
39  {
40  	private static int BUFFER_SIZE = 1024;
41  	/**
42  	 * The physical block size on the target device (512 byte by default)
43  	 */
44  	private static int BLOCK_SIZE = 512;
45  	private static Log log = LogFactory.getLog(FileUtil.class);
46  	public static String UNIX_FILE_SEPARATOR = "/";
47  
48  	private FileUtil()
49  	{
50  	}
51  	
52  	/**
53  	 * Creates a ".gz" file of the given input file.
54  	 * @param inputFile
55  	 * @return Returns the newly created file
56  	 */
57  	public static File createGzip(File inputFile) 
58  	{
59  		File targetFile = new File(inputFile.getParentFile(), inputFile.getName() + ".gz");
60  		if(targetFile.exists()) {
61  			log.warn("The target file '" + targetFile + "' already exists. Will overwrite");
62  		}
63  		
64  		FileInputStream in = null;
65  		GZIPOutputStream out = null;
66  		try {
67  			int read = 0;
68  			byte[] data = new byte[BUFFER_SIZE];
69  			in = new FileInputStream(inputFile);
70  			// write gzipped file
71  			out = new GZIPOutputStream(new FileOutputStream(targetFile));
72  			// write all data
73  			while((read = in.read(data, 0, BUFFER_SIZE)) != -1) {
74  				out.write(data, 0, read);
75  			}
76  			in.close();
77  			out.close();
78  			// Delete original file if everything worked fine
79  			boolean deleteSuccess = inputFile.delete();
80  			if(!deleteSuccess) {
81  				log.warn("Could not delete file '" + inputFile + "'");
82  			}
83  			log.info("Successfully created gzip file '" + targetFile + "'.");
84          }
85          catch(Exception e) {
86          	log.error("Exception while creating GZIP.", e);
87          }
88          finally {
89          	StreamUtil.tryCloseStream(in);
90          	StreamUtil.tryCloseStream(out);
91          }
92          return targetFile;
93  	}
94  
95  	
96  	/**
97  	 * Creates a tar file of all files in the given directory.
98  	 * @param directoryToPack The directory to be packed
99  	 * @param targetTarFile The target file for storing the new tar
100 	 * @throws IOException
101 	 */
102 	public static void createTar(File directoryToPack, File targetTarFile) throws IOException
103 	{
104 		if (directoryToPack == null) {
105 			throw new NullPointerException(
106 					"The parameter 'directoryToPack' must not be null");
107 		}
108 		
109 		if (targetTarFile == null) {
110 			throw new NullPointerException(
111 					"The parameter 'targetTarFile' must not be null");
112 		}
113 
114 		if(!directoryToPack.exists() || !directoryToPack.isDirectory()) {
115 			throw new IllegalArgumentException("The target file '" + directoryToPack + "' does not exist or is not a directory.");
116 		}
117 		if(targetTarFile.exists()) {
118 			log.warn("The target file '" + targetTarFile + "' already exists. Will overwrite");
119 		}
120 
121 		log.debug("Creating tar from all files in directory '" + directoryToPack + "'");
122 		
123 		byte buffer[] = new byte[BUFFER_SIZE];
124 		// Open archive file
125 		FileOutputStream targetOutput = new FileOutputStream(targetTarFile);
126 		TarOutputStream targetOutputTar = new TarOutputStream(targetOutput);
127 //targetOutputTar.setDebug(true);
128 //targetOutputTar.setBufferDebug(true);
129 //targetOutputTar.setLongFileMode(TarOutputStream.LONGFILE_GNU);
130 		try {
131 			List<File> fileList = collectFiles(directoryToPack);
132 			
133 			for(Iterator<File> iter=fileList.iterator(); iter.hasNext(); ) {
134 				File file=iter.next();
135 				if (file == null || !file.exists() || file.isDirectory()) {
136 					log.info("The file '" + file + "' is ignored - is a directory or non-existent");
137 					continue;
138 				}
139 				
140 				if(file.equals(targetTarFile)) {
141 					log.debug("Skipping file: '" + file + "' - is the tar file itself");
142 					continue;
143 				}
144 				
145 				log.debug("Adding to archive: file='" + file + "', archive='" + targetTarFile + "'");
146 				
147 				// Create the file path for this file within the tar (relative directories)
148 				String filePathInTar = getFilePathInTar(file, directoryToPack);
149 				log.debug("File path in tar: '" + filePathInTar + "' (file=" + file + ")");
150 				// Add archive entry
151 				TarEntry tarAdd = new TarEntry(file);
152 				tarAdd.setModTime(file.lastModified());
153 				tarAdd.setName(filePathInTar);
154 				targetOutputTar.putNextEntry(tarAdd);
155 				// Write file content to archive - not for directories
156 				if(file.isFile()) {
157 					FileInputStream in = new FileInputStream(file);
158 					try {
159 						while (true) {
160 							int nRead = in.read(buffer, 0, buffer.length);
161 							if (nRead <= 0)
162 								break;
163 							targetOutputTar.write(buffer, 0, nRead);
164 						}
165 					}
166 					finally {
167 						StreamUtil.tryCloseStream(in);
168 					}
169 				}
170 				targetOutputTar.closeEntry();
171 			}
172 		}
173 		finally {
174 			StreamUtil.tryCloseStream(targetOutputTar);
175 			StreamUtil.tryCloseStream(targetOutput);
176 		}
177 		log.info("Tar Archive created successfully '" + targetTarFile + "'");
178 	}		
179 
180 	
181 	/**
182 	 * Resolves the relative path of the given file within the given directoryToPack.
183 	 * @param file
184 	 * @param directoryToPack
185 	 * @return The relative path of the file that can be used as filename in the tar
186 	 */
187 	static String getFilePathInTar(File file, File directoryToPack) 
188 	{
189 		String result = file.getName();
190 		for(File currentFileToCheck=file.getParentFile(); !currentFileToCheck.equals(directoryToPack); currentFileToCheck=currentFileToCheck.getParentFile()) {
191 			result = currentFileToCheck.getName() + "/" + result;
192 		}
193 		return result;
194 	}
195 
196 	private static List<File> collectFiles(File directoryToPack) {
197 		List<File> result = new ArrayList<File>();
198 		File[] fileList = directoryToPack.listFiles();
199 		for (int i = 0; i < fileList.length; i++) {
200 			File file = fileList[i];
201 			result.add(file);
202 			if(file.isDirectory()) {
203 				// Recurse into the directory
204 				List<File> children = collectFiles(file);
205 				result.addAll(children);
206 			}
207 		}
208 		return result;
209 	}
210 
211 	public static void deleteRecursively(File fileOrDirectory) {
212 		if(fileOrDirectory.isFile()) {
213 			boolean success = fileOrDirectory.delete();
214 			if(!success) {
215 				throw new RuntimeException("The file '" + fileOrDirectory.getAbsolutePath() + "' could not be deleted");
216 			}
217 			else {
218 				log.debug("Successfully deleted file '" + fileOrDirectory + "'.");
219 			}
220 		}
221 		else {
222 			// Directory. List the files
223 			File[] children = fileOrDirectory.listFiles();
224 			for (int i = 0; i < children.length; i++) {
225 				File file = children[i];
226 				deleteRecursively(file);
227 			}
228 			// After all files were deleted, delete the directory itself
229 			boolean success = fileOrDirectory.delete();
230 			if(!success) {
231 				throw new RuntimeException("The directory '" + fileOrDirectory.getAbsolutePath() + "' could not be deleted");
232 			}
233 			else {
234 				log.debug("Successfully deleted directory '" + fileOrDirectory + "'.");
235 			}
236 		}
237 	}
238 	
239 	
240 	public static void writeFile(File target, String content) throws IOException
241 	{
242 		Charset charset = Charset.defaultCharset();
243 		log.info("Using default character set " + charset + " for writing data to file " + target);
244 		writeFile(target, content, charset);
245 	}
246 	
247 	public static void writeFile(File target, String content, Charset charset) throws IOException
248 	{
249 		OutputStream output = new BufferedOutputStream(new FileOutputStream(target));
250 		try {
251 			Writer writer = new OutputStreamWriter(output, charset);
252 			writer.write(content);
253 			writer.flush();
254 		}
255 		finally {
256 			StreamUtil.tryCloseStream(output);
257 		}
258 	}
259 
260 	public static String readFile(File file) throws IOException 
261 	{
262 		if (file == null) {
263 			throw new NullPointerException(
264 					"The parameter 'file' must not be null");
265 		}
266 		
267 		InputStream input = new BufferedInputStream(new FileInputStream(file));
268 		try {
269 			return FileUtil.readString(input);
270 		}
271 		finally {
272 			StreamUtil.tryCloseStream(input);
273 		}
274 	}
275 
276 	public static String readString(URL file) throws IOException 
277 	{
278 		if (file == null) {
279 			throw new NullPointerException(
280 					"The parameter 'file' must not be null");
281 		}
282 		
283 		InputStream input = file.openStream();
284 		try {
285 			return FileUtil.readString(input);
286 		}
287 		finally {
288 			StreamUtil.tryCloseStream(input);
289 		}
290 	}
291 
292 	/**
293 	 * Reads the bytes from the given input stream using the default platform character set.
294 	 * It is strongly recommended that you use the method {@link FileUtil#readString(InputStream, Charset)} to
295 	 * ensure that the character conversion is correctly done.
296 	 * @param input
297 	 * @return
298 	 * @throws IOException
299 	 */
300 	public static String readString(InputStream input) throws IOException {
301 		log.info("Using default character set " + Charset.defaultCharset() + " for reading input stream...");
302 		return FileUtil.readString(input, Charset.defaultCharset());
303 	}
304 
305 	/**
306 	 * Reads the string from the given input stream using the given charset. Does not close the stream after reading.
307 	 * @param input
308 	 * @param charset
309 	 * @return
310 	 * @throws IOException
311 	 */
312 	public static String readString(InputStream input, Charset charset) throws IOException {
313 		Reader reader = new InputStreamReader(input, charset);
314 
315 		StringBuffer sb = new StringBuffer();
316 		int character = -1;
317 		while((character=reader.read()) != -1) {
318 			sb.append((char)character);
319 		}
320 		return sb.toString();
321 	}
322 
323 	
324 	public static boolean isSubdir(File subDirToCheck, File basedir) {
325 		if(subDirToCheck.equals(basedir)) {
326 			return true;
327 		}
328 		else {
329 			File parentOfSubdir = subDirToCheck.getParentFile();
330 			if(parentOfSubdir == null) {
331 				// Is not a subdir - end recursion
332 				return false;
333 			}
334 			else {
335 				// Recurse
336 				return FileUtil.isSubdir(parentOfSubdir, basedir);
337 			}
338 		}
339 	}
340 
341 	/**
342 	 * Builds a path and separates the given file or directory using the given pathSeparator.
343 	 * @param fileOrDir
344 	 * @param pathSeparator
345 	 * @return
346 	 */
347 	public static String buildPath(File fileOrDir, String pathSeparator) {
348 
349 		if(File.separator.equals(pathSeparator)) {
350 			log.info("The file separator is already a '" + File.separator + "'. Nothing to do here.");
351 			return fileOrDir.getPath();
352 		}
353 		
354 		String result = "";
355 		for(File current=fileOrDir; current!=null; current=current.getParentFile() ) {
356 			String name = current.getName();
357 			if(current.isFile()) {
358 				result = name; 
359 			}
360 			else {
361 				result = name + pathSeparator + result;
362 			}
363 		}
364 		return result;
365 	}
366 	
367 	/**
368 	 * Returns all files and directories from the given dir recursively 
369 	 * @param destDir
370 	 * @param filter
371 	 * @return
372 	 */
373 	public static List<File> getFiles(File destDir, FileFilter filter) {
374 		List<File> resultList = new ArrayList<File>();
375 		File[] children = null;
376 		if(filter != null) {
377 			children = destDir.listFiles();
378 		}
379 		else {
380 			children = destDir.listFiles(filter);
381 		}
382 		if(children != null) {
383 			resultList.addAll(Arrays.asList(children));
384 
385 			// Iterate over the children
386 			for (int i = 0; i < children.length; i++) {
387 				// recurse
388 				List<File> childrenResult = getFiles(children[i], filter);
389 				resultList.addAll(childrenResult);
390 			}
391 		}
392 
393 		return resultList;
394 	}
395 
396 	/**
397 	 * Recursively copies the source directory with all files to the target directory. Includes all subdirectories and their files.
398 	 * @param sourceDir
399 	 * @param targetDir
400 	 * @param fileFilter File filter used for copying. Can be null which will include all files to the copy process.
401 	 * @throws IOException
402 	 */
403 	public static void copyFiles(File sourceDir, File targetDir, FileFilter fileFilter) throws IOException 
404 	{
405 		if(!sourceDir.isDirectory()) {
406 			throw new IllegalArgumentException("The given sourceDir '" + sourceDir + "' must be a directory.");
407 		}
408 		if(!sourceDir.isDirectory()) {
409 			throw new IllegalArgumentException("The given targetDir '" + targetDir + "' must be a directory.");
410 		}
411 		
412 		File[] sourceChildren = null;
413 		if(fileFilter == null) {
414 			sourceChildren = sourceDir.listFiles();
415 		}
416 		else {
417 			sourceChildren = sourceDir.listFiles(fileFilter);
418 		}
419 		
420 		// Abort recursion
421 		if(sourceChildren == null) {
422 			return;
423 		}
424 		
425 		List<File> fileList = Arrays.asList(sourceChildren);
426 		for(Iterator<File> iter=fileList.iterator(); iter.hasNext(); ) {
427 			File file = iter.next();
428 			File fileInTarget = new File(targetDir, file.getName());
429 			if(file.isDirectory()) {
430 				boolean dirSuccess = fileInTarget.mkdir();
431 				if(!dirSuccess) {
432 					throw new RuntimeException("Could not create directory " + targetDir + ". Aborting");
433 				}
434 				
435 				log.debug("Created directory '" + fileInTarget + "'");
436 				// Now recurse into the directory
437 				FileUtil.copyFiles(file, fileInTarget, fileFilter);
438 			}
439 			else {
440 				// Just copy the file
441 				FileUtil.copyFile(file, fileInTarget);
442 			}
443 		}
444 	}
445 
446 	/**
447 	 * Copies one file to a different location
448 	 * @param sourceFile
449 	 * @param destFile The destination file
450 	 * @throws IOException
451 	 */
452 	public static void copyFile(File sourceFile, File destFile)
453 			throws IOException 
454 	{
455 		log.info("Copying file '" + sourceFile + "' to '" + destFile + "'");
456 		
457 		if(!sourceFile.isFile()) {
458 			throw new IllegalArgumentException("The sourceFile '" + sourceFile + "' does not exist or is not a normal file.");
459 		}
460 
461 		if (!destFile.exists()) {
462 			destFile.createNewFile();
463 		}
464 		
465 		FileChannel source = null;
466 		FileChannel destination = null;
467 		try {
468 			source = new FileInputStream(sourceFile).getChannel();
469 			destination = new FileOutputStream(destFile).getChannel();
470 			long numberOfBytes = destination.transferFrom(source, 0, source.size());
471 			log.debug("Transferred " + numberOfBytes + " bytes from '" + sourceFile + "' to '" + destFile + "'.");
472 		} finally {
473 			if (source != null) {
474 				source.close();
475 			}
476 			if (destination != null) {
477 				destination.close();
478 			}
479 		}
480 	}
481 
482 	public static void createDir(File dir, boolean failOnError) {
483 		boolean success = dir.mkdir();
484 		if(success) {
485 			log.debug("Successfully created directory '" + dir + "'");
486 		}
487 		else {
488 			if(dir.isDirectory()) {
489 				log.debug("The directory '" + dir + "' already exists. Nothing to do.");
490 				return;
491 			}
492 
493 			String errorMessage = "Could not create '" + dir + "'.";
494 			// Check if the parent directory exists - if not add this information to the error message
495 			File parent = dir.getParentFile();
496 			if(!parent.exists()) {
497 				errorMessage += " The parent directory '" + parent + "' does not exist.";
498 			}
499 			
500 			// Not successfully created the dir - handle the failure
501 			if(failOnError) {
502 				throw new IllegalStateException(errorMessage);
503 			}
504 			else {
505 				log.warn(errorMessage);
506 			}
507 		}
508 		
509 	}
510 
511 	/**
512 	 * Determines if the given unix path is a relocatable path or not in the sense of a prototype file.
513 	 * It is relocatable if the path is relative (not starting with a '/').
514 	 * @param path The path to check.
515 	 * @return
516 	 */
517 	public static boolean isRelocatable(String path) {
518 		if(path == null) {
519 			return false;
520 		}
521 		return !path.startsWith(UNIX_FILE_SEPARATOR);
522 	}
523 
524 
525 	/**
526 	 * Recursively counts the files in the given directory. Subdirectories are also counted.
527 	 * @param directory
528 	 * @return The number of files/directories in the given directory
529 	 */
530 	public static int countFilesAndDirectories(File directory) {
531 		return countFiles(directory, true);
532 	}
533 
534 	/**
535 	 * Recursively counts the files in the given directory. Subdirectories are not counted.
536 	 * @param directory
537 	 * @return The number of files in the given directory that are no directories
538 	 */
539 	public static int countFiles(File directory) {
540 		return countFiles(directory, false);
541 	}
542 
543 	/**
544 	 * Recursively counts the files in the given directory. Subdirectories are not counted.
545 	 * @param directory
546 	 * @param countDirectories If subdirectories should also be included into the count
547 	 * @return The number of files in the given directory that are no directories
548 	 */
549 	public static int countFiles(File directory, boolean countDirectories) {
550 		if(directory==null) {
551 			throw new NullPointerException("The parameter 'directory' must not be null");
552 		}
553 		int count = 0;
554 		File[] files = directory.listFiles();
555 		if(files != null) {
556 			for (int i = 0; i < files.length; i++) {
557 				if(files[i].isDirectory()) {
558 					if(countDirectories) 
559 						count++;
560 					count += countFiles(files[i]);
561 				}
562 				else {
563 					count++;
564 				}
565 			}
566 		}
567 		return count;
568 	}
569 
570 	/**
571 	 * Returns the number of 512 byte blocks that are needed to store the given directory.
572 	 * @param basedir 
573 	 * @return
574 	 */
575 	public static Long getBlockCount(File basedir) {
576 		if(basedir == null) {
577 			throw new NullPointerException("The parameter 'basedir' must not be null.");
578 		}
579 		long fileSize = FileUtil.getSize(basedir);
580 		long blockCount = fileSize / BLOCK_SIZE + (fileSize % BLOCK_SIZE>0 ? 1 : 0); // a block consists of 512 bytes
581 		return new Long(blockCount);
582 	}
583 
584 
585 	/**
586 	 * @param basedir
587 	 * @return Returns the size in bytes of the content of the given directory
588 	 */
589 	public static long getSize(File basedir) {
590 		if(basedir == null) {
591 			throw new NullPointerException("The parameter 'basedir' must not be null.");
592 		}
593 		File[] children = basedir.listFiles();
594 		long overallSize = 0;
595 		for (int i = 0; i < children.length; i++) {
596 			File currentFile = children[i];
597 			if(currentFile.isFile()) {
598 				overallSize += currentFile.length();
599 			}
600 			else {
601 				// Recurse into the directory
602 				long childSize = getSize(currentFile);
603 				overallSize += childSize;
604 			}
605 		}
606 		return overallSize;
607 	}
608 
609 	
610 	/**
611 	 * Non-recursively checks the content of the given directory against the given array of file objects.
612 	 * @param dirToCheck
613 	 * @param expectedFiles
614 	 */
615 	public static void assertContainsFiles(File dirToCheck, File[] expectedFiles) {
616 		File[] actualFiles = dirToCheck.listFiles();
617 		List<File> actualFileList = Arrays.asList(actualFiles);
618 		List<File> expectedFileList = Arrays.asList(expectedFiles);
619 		boolean equal = actualFileList.equals(expectedFileList);
620 		if(!equal) {
621 			throw new PackageException("The files in directory '" + dirToCheck + "' are not equal to the given array of files. " +
622 					"Expected: " + expectedFileList + ",   Actual: " + actualFileList);
623 		}
624 	}
625 	
626 }