View Javadoc

1   /*
2    *  XNap Commons
3    *
4    *  Copyright (C) 2005  Felix Berger
5    *  Copyright (C) 2005  Steffen Pingel
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2.1 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free Software
19   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   */
21  package org.xnap.commons.util;
22  
23  import java.io.BufferedInputStream;
24  import java.io.BufferedOutputStream;
25  import java.io.BufferedReader;
26  import java.io.BufferedWriter;
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileNotFoundException;
30  import java.io.FileOutputStream;
31  import java.io.FileWriter;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.InputStreamReader;
35  import java.io.NotSerializableException;
36  import java.io.ObjectInputStream;
37  import java.io.ObjectOutputStream;
38  import java.io.OutputStream;
39  import java.io.RandomAccessFile;
40  import java.util.Collection;
41  import java.util.Iterator;
42  import java.util.LinkedList;
43  import java.util.List;
44  import java.util.Properties;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.xnap.commons.io.NullProgressMonitor;
48  import org.xnap.commons.io.ProgressMonitor;
49  
50  /***
51   * Provides a set of static methods that help with file manipulation.
52   */
53  public class FileHelper {
54  	
55  	private static Log logger = LogFactory.getLog(FileHelper.class);
56  
57  	/***
58  	 * Creates a unique file by inserting digits into <code>pathname</code>.
59  	 *
60  	 * @return the created file
61  	 */
62  	public static synchronized File createUnique(String pathname) 
63  		throws IOException
64  	{
65  		logger.debug("FileHelper: creating unique: " + pathname);
66  
67  		File f = new File(uniqueName(pathname));
68  		f.createNewFile();
69  
70  		return f;
71  	}
72  
73  
74  	/***
75  	 * Creates a unique file in by inserting digits into <code>filename</code>.
76  	 *
77  	 * @param child the name of the file to create
78   	 * @param parent the path of the file to create
79  	 * @return the created file
80  	 */
81  	public static synchronized File createUnique(File parent, String child)
82  		throws IOException
83  	{
84  		if (parent.isDirectory() || parent.mkdirs()) {
85  			return createUnique
86  				(parent.getAbsolutePath() + File.separator + child);
87  		}
88  		throw new FileNotFoundException();
89  	}
90  
91  	/***
92  	 * Moves <code>file</code> to <code>path</code> but renames 
93  	 * <code>filename</code> if it already exists in the target path.
94  	 *
95  	 * @param file the file to move
96  	 * @param path the target path
97  	 * @param filename the new filename for <code>file</code>
98  	 * @return the moved file
99  	 */
100 	public static synchronized File moveUnique
101 		(File file, String path, String filename) throws IOException
102 	{
103 		String newFilename = appendSeparator(path) + filename;
104 		logger.debug("moveUnique new name: " + newFilename);
105 		if (newFilename.equals(file.getAbsolutePath())) {
106 			return file;
107 		}
108 	
109 		File p = new File(path);
110 		if (p.isDirectory() || p.mkdirs()) {
111 			File newFile = new File(uniqueName(newFilename));
112 			logger.debug("moveUnique new file: " + newFile);
113 			if (move(file, newFile)) {
114 				return newFile;
115 			}
116 			else {
117 				throw new FileNotFoundException
118 					("Could not rename " + file.getAbsolutePath() + " to "
119 					 + newFile.getAbsolutePath());
120 			}
121 		}
122 		else {
123 			throw new FileNotFoundException
124 				("Could not create " + p.getAbsolutePath());
125 		}
126 	}
127 
128 	/***
129 	 * Moves <code>file</code> to <code>path</code>.
130 	 *
131 	 * @see #moveUnique(File, String, String)
132 	 */
133 	public static synchronized File moveUnique(File file, String path) 
134 		throws IOException
135 	{
136 		return moveUnique(file, path, file.getName());
137 	}
138 	
139 	/***
140 	 * Moves a file. Tries a rename, if fails, copies file.
141 	 */
142 	public static boolean move(File source, File dest) throws IOException 
143 	{
144 		if (!source.renameTo(dest)) {
145 			copy(source, dest);
146 			source.delete();
147 		}
148 
149 		return true;
150 	}
151 
152 	public static void copy(File source, File dest) throws IOException
153 	{
154 		copy(source, dest, NullProgressMonitor.MONITOR);
155 	}
156 	
157 	/***
158 	 * Copies <code>source</code> to <code>dest</code>. If dest already exists
159 	 * it is overwritten.
160 	 *
161 	 * @param source the source file
162 	 * @param dest the destination file
163 	 */
164 	public static void copy(File source, File dest, ProgressMonitor monitor)
165 		throws IOException 
166 	{
167 		InputStream in = new FileInputStream(source);
168 		monitor.setTotalSteps(source.length());
169 		try {
170 			copy(in, new FileOutputStream(dest), monitor);
171 			// TODO delete dest again if monitor is cancelled?
172 		}
173 		finally {
174 			try {
175 				in.close();
176 			}
177 			catch (IOException e) {
178 			}
179 		}
180 	}
181 
182 	public static void copy(InputStream inStream, OutputStream outStream)
183 		throws IOException
184 	{
185 		copy(inStream, outStream, NullProgressMonitor.MONITOR);
186 	}
187 	
188 	/***
189 	 * Copies <code>source</code> to <code>dest</code>. If dest already exists
190 	 * it is overwritten.
191 	 *
192 	 * @param inStream the source data
193 	 * @param outStream the destination data
194 	 */
195 	public static void copy(InputStream inStream, OutputStream outStream, 
196 			ProgressMonitor monitor)
197 		throws IOException 
198 	{
199 		BufferedInputStream in = null;
200 		BufferedOutputStream out = null;
201 		try {
202 			in = new BufferedInputStream(inStream);
203 			out = new BufferedOutputStream(outStream);
204 
205 			byte buffer[] = new byte[512 * 1024];
206 			int count;
207 			while (!monitor.isCancelled() 
208 					&& (count = in.read(buffer, 0, buffer.length)) != -1) {
209 				out.write(buffer, 0, count);
210 				monitor.work(count);
211 			}
212 			out.flush();
213 		}
214 		finally {
215 			if (in != null) {
216 				try {
217 					in.close();
218 				}
219 				catch (IOException e) {
220 				}
221 			}
222 			if (out != null) {
223 				try {
224 					out.close();
225 				}
226 				catch (IOException e) {
227 				}
228 			}	   
229 		}
230 	}
231 
232 	/***
233 	 * Returns the lower case extension part of <code>filename</code>. 
234 	 *
235 	 * @see #name(String)
236 	 */
237 	public static String extension(String filename) 
238 	{
239 		return StringHelper.lastToken(filename, ".").toLowerCase();
240 	}
241 
242 	/***
243 	 * Returns the name part of <code>filename</code> without 
244 	 * its extension.
245 	 *
246 	 * @see #extension(String)
247 	 */
248 	public static String name(String filename) 
249 	{
250 		int i = filename.lastIndexOf(".");
251 		return (i < 1) ? filename : filename.substring(0, i);
252 	}
253 
254 	/***
255 	 * Creates unique filename.
256 	 */
257 	public static String uniqueName(String filename)
258 	{
259 		return uniqueName(filename, "");
260 	}
261 
262 	public static String uniqueName(String filename, String infix)
263 	{
264 		String extension = extension(filename);
265 
266 		if (extension.length() > 0) {
267 			extension = "." + extension;
268 			filename = name(filename);
269 		}
270 	 
271 		if (infix.length() > 0) {
272 			infix = "." + infix;
273 		}
274 	
275 		if (exists(filename + infix + extension)) {
276 			for (int i = 1; ; i++) {
277 				if (!exists(filename + infix + "." + i + extension))
278 					return (filename + infix + "." + i + extension);
279 			}
280 		}
281 	
282 		return filename + infix + extension;
283 	}
284 
285 	/***
286 	 * Returns true, if the file denoting <code>pathname</code> exists,
287 	 * false otherwise.
288 	 */
289 	public static boolean exists(String pathname)
290 	{
291 		return (new File(pathname)).exists();
292 	}
293 
294 	/***
295 	 * Checks for existence of .xnap folder in the user's home directory and
296 	 * returns the absolute path with a file separator appended.
297 	 *
298 	 * @param subdir a sub directory that is located in the applications settings directory or created if
299 	 * it does not exist
300 	 * @return empty string, if subdir could not be created; absolute path,
301 	 * otherwise
302 	 */
303 	public static final String getHomeDir(String appname, String subdir)
304 		throws IOException
305 	{
306 		StringBuilder sb = new StringBuilder();
307 
308 		sb.append(System.getProperty("user.home"));
309 		sb.append(File.separatorChar);
310 		
311 		if (appname != null && appname.length() > 0) {
312 			sb.append(".");
313 			sb.append(appname);
314 			sb.append(File.separatorChar);
315 		}
316 		
317 		if (subdir != null && subdir.length() > 0) {
318 			sb.append(subdir);
319 			sb.append(File.separatorChar);
320 		}
321 		String dir = sb.toString();
322 
323 		File file = new File(dir);
324 		if (file.isDirectory() || file.mkdirs()) {
325 			return dir;
326 		}
327 
328 		throw new IOException("Could not create directory");
329 	}
330 
331 	/***
332 	 * Returns the absolute path of the applications settings directory.
333 	 *
334 	 * @see #getHomeDir(String)
335 	 */
336 	public static final String getHomeDir(String appname) throws IOException
337 	{
338 		return getHomeDir(appname, null);
339 	}
340 
341 	/***
342 	 * Appens a file separator to <code>path</code> if it does not have a
343 	 * trailing one.
344 	 */
345 	public static String appendSeparator(String path) 
346 	{
347 		if (path.length() > 0 && !path.endsWith(File.separator)) {
348 			return path + File.separator;
349 		}
350 		else {
351 			return path;
352 		}
353 	}
354 
355 	/***
356 	 * Shortens <code>file</code> by <code>bytes</code> bytes.
357 	 */
358 	public static void shorten(File file, long bytes) throws IOException
359 	{
360 		RandomAccessFile f = new RandomAccessFile(file, "rw");
361 		try {
362 			f.setLength(Math.max(f.length() - bytes, 0));
363 		}
364 		finally {
365 			try {
366 				f.close();
367 			} catch (IOException e) {}
368 		}
369 	}
370 
371 	/***
372 	 * Stores <code>props</code> in <code>file</code>.
373 	 */
374 	public static void writeProperties(File file, Properties props)
375 		throws IOException
376 	{
377 		FileOutputStream out = null;
378 		try {
379 			out = new FileOutputStream(file);
380 			props.store(out, "This file was automatically generated.");
381 		} 
382 		catch (IOException e) {
383 			throw(e);
384 		}
385 		finally {
386 			try {
387 				if (out != null) {
388 					out.close();
389 				}
390 			}
391 			catch (Exception e) {
392 			}
393 		}
394 	}
395 
396 	/***
397 	 * Reads all objects from <code>file</code> using serialization and adds 
398 	 * them to <code>c</code>.
399 	 */
400 	public static void readBinary(File file, Collection<Object> c) throws IOException
401 	{
402 		logger.debug("reading binary file: " + file);
403 
404 		ObjectInputStream in = null;
405 		try {
406 			in = new ObjectInputStream(new FileInputStream(file));
407 
408 			int size = in.readInt();
409 			for (int i = 0; i < size; i++) {
410 				try {
411 					Object o = in.readObject();
412 					if (o != null) {
413 						c.add(o);
414 					}
415 				}
416 				catch (IOException e) {
417 					throw e;
418 				}
419 				catch (Exception e) {
420 					logger.warn("error while reading binary file", e);
421 				}
422 			}
423 		}
424 		finally {
425 			try {
426 				if (in != null) {
427 					in.close();
428 				}
429 			}
430 			catch (IOException e) {
431 			}
432 		}
433 	}
434 
435 	public static String readText(File file) throws IOException
436 	{
437 		return readText(new FileInputStream(file));
438 	}
439 
440 
441 	/***
442 	 * Reads a text file.
443 	 */
444 	public static String readText(InputStream inStream) throws IOException
445 	{
446 		BufferedReader in 
447 			= new BufferedReader(new InputStreamReader(inStream));
448 		try {
449 			StringBuilder sb = new StringBuilder();
450 			char buffer[] = new char[4 * 1024];
451 			int count;
452 			while ((count = in.read(buffer, 0, buffer.length)) != -1) {
453 				sb.append(buffer, 0, count);
454 			}
455 			return sb.toString();
456 		}
457 		finally {
458 			try {
459 				in.close();
460 			}
461 			catch (IOException e) {
462 				// this exception gets lost
463 			}
464 		}
465 	}
466 
467 	/***
468 	 * TODO provide a symmetrical write function?
469 	 * @param file
470 	 * @throws IOException
471 	 */
472 	public static String[] readConfig(File file) throws IOException
473 	{
474 		return readConfig(new FileInputStream(file));
475 	}
476 
477 	/***
478 	 * Reads a text file. Ignores all lines empty lines and lines that
479 	 * start with a '#' character.
480 	 *
481 	 * @return always a valid array, possibly with size 0
482 	 */
483 	public static String[] readConfig(InputStream inStream) throws IOException
484 	{
485 		BufferedReader in
486 			= new BufferedReader(new InputStreamReader(inStream));
487 		try {
488 			List<String> lines = new LinkedList<String>();
489 
490 			String s;
491 			while ((s = in.readLine()) != null) {
492 				s = s.trim();
493 				if (s.length() == 0 || s.startsWith("#")) {
494 					continue;
495 				}
496 				lines.add(s);
497 			}	
498 			return lines.toArray(new String[0]);
499 		}
500 		finally {
501 			try {
502 				in.close();
503 			}
504 			catch (IOException e) {
505 				// this exception gets lost
506 			}
507 		}
508 	}
509 
510 	/***
511 	 * Write all items in <code>c</code> to <code>file</code> using 
512 	 * serialization.
513 	 */
514 	public static void writeBinary(File file, Collection c) throws IOException
515 	{
516 		logger.debug("writing " + c.size() + " items to binary file: " + file);
517 
518 		ObjectOutputStream out = null;
519 		try {
520 			out = new ObjectOutputStream(new FileOutputStream(file));
521 
522 			out.writeInt(c.size());
523 			for (Iterator i = c.iterator(); i.hasNext();) {
524 				try {
525 					out.writeObject(i.next());
526 				}
527 				catch (NotSerializableException e) {
528 					logger.debug("Object not serializable", e);
529 				}
530 			}
531 		}
532 		finally {
533 			try {
534 				if (out != null) {
535 					out.close();
536 				}
537 			}
538 			catch (IOException e) {
539 			}
540 		}
541 	}
542 
543 	/***
544 	 * Writes a text file.
545 	 */
546 	public static void writeText(File file, String text) throws IOException
547 	{
548 		BufferedWriter out = new BufferedWriter(new FileWriter(file));
549 		try {
550 			out.write(text);
551 		}
552 		finally {
553 			try {
554 				out.close();
555 			}
556 			catch (IOException e) {
557 				// this exception gets lost
558 			}
559 		}
560 	}
561 
562 }