001: /*****************************************************************************
002: * Java Plug-in Framework (JPF)
003: * Copyright (C) 2004-2007 Dmitry Olshansky
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *****************************************************************************/package org.java.plugin.util;
019:
020: import java.io.BufferedInputStream;
021: import java.io.BufferedOutputStream;
022: import java.io.ByteArrayInputStream;
023: import java.io.ByteArrayOutputStream;
024: import java.io.File;
025: import java.io.FileFilter;
026: import java.io.FileInputStream;
027: import java.io.FileNotFoundException;
028: import java.io.FileOutputStream;
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.io.OutputStream;
032: import java.io.UnsupportedEncodingException;
033: import java.net.MalformedURLException;
034: import java.net.URL;
035: import java.net.URLDecoder;
036: import java.util.Calendar;
037: import java.util.Date;
038: import java.util.Locale;
039: import java.util.jar.JarFile;
040: import java.util.zip.ZipEntry;
041:
042: /**
043: * Input/Output, File and URL/URI related utilities.
044: *
045: * @version $Id: IoUtil.java,v 1.9 2007/04/17 17:39:52 ddimon Exp $
046: */
047: public final class IoUtil {
048: private static final String PACKAGE_NAME = "org.java.plugin.util"; //$NON-NLS-1$
049:
050: /**
051: * Copies one file, existing file will be overridden.
052: * @param src source file to copy FROM
053: * @param dest destination file to copy TO
054: * @throws IOException if any I/O error has occurred
055: */
056: public static void copyFile(final File src, final File dest)
057: throws IOException {
058: if (!src.isFile()) {
059: throw new IOException(ResourceManager.getMessage(
060: PACKAGE_NAME, "notAFile", src)); //$NON-NLS-1$
061: }
062: if (dest.isDirectory()) {
063: throw new IOException(ResourceManager.getMessage(
064: PACKAGE_NAME, "isFolder", dest)); //$NON-NLS-1$
065: }
066: BufferedInputStream in = new BufferedInputStream(
067: new FileInputStream(src));
068: try {
069: BufferedOutputStream out = new BufferedOutputStream(
070: new FileOutputStream(dest, false));
071: try {
072: copyStream(in, out, 1024);
073: } finally {
074: out.close();
075: }
076: } finally {
077: in.close();
078: }
079: dest.setLastModified(src.lastModified());
080: }
081:
082: /**
083: * Copies folder recursively, existing files will be overridden
084: * @param src source folder
085: * @param dest target folder
086: * @throws IOException if any I/O error has occurred
087: */
088: public static void copyFolder(final File src, final File dest)
089: throws IOException {
090: copyFolder(src, dest, true, false, null);
091: }
092:
093: /**
094: * Copies folder, existing files will be overridden
095: * @param src source folder
096: * @param dest target folder
097: * @param recursive if <code>true</code>, processes folder recursively
098: * @throws IOException if any I/O error has occurred
099: */
100: public static void copyFolder(final File src, final File dest,
101: final boolean recursive) throws IOException {
102: copyFolder(src, dest, recursive, false, null);
103: }
104:
105: /**
106: * Copies folder.
107: * @param src source folder
108: * @param dest target folder
109: * @param recursive if <code>true</code>, processes folder recursively
110: * @param onlyNew if <code>true</code>, target file will be overridden if it
111: * is older than source file only
112: * @throws IOException if any I/O error has occurred
113: */
114: public static void copyFolder(final File src, final File dest,
115: final boolean recursive, final boolean onlyNew)
116: throws IOException {
117: copyFolder(src, dest, recursive, onlyNew, null);
118: }
119:
120: /**
121: * Copies folder.
122: * @param src source folder
123: * @param dest target folder
124: * @param recursive if <code>true</code>, processes folder recursively
125: * @param onlyNew if <code>true</code>, target file will be overridden if it
126: * is older than source file only
127: * @param filter file filter, optional, if <code>null</code> all files will
128: * be copied
129: * @throws IOException if any I/O error has occurred
130: */
131: public static void copyFolder(final File src, final File dest,
132: final boolean recursive, final boolean onlyNew,
133: final FileFilter filter) throws IOException {
134: if (!src.isDirectory()) {
135: throw new IOException(ResourceManager.getMessage(
136: PACKAGE_NAME, "notAFolder", src)); //$NON-NLS-1$
137: }
138: if (dest.isFile()) {
139: throw new IOException(ResourceManager.getMessage(
140: PACKAGE_NAME, "isFile", dest)); //$NON-NLS-1$
141: }
142: if (!dest.exists() && !dest.mkdirs()) {
143: throw new IOException(ResourceManager.getMessage(
144: PACKAGE_NAME, "cantMakeFolder", dest)); //$NON-NLS-1$
145: }
146: File[] srcFiles = (filter != null) ? src.listFiles(filter)
147: : src.listFiles();
148: for (int i = 0; i < srcFiles.length; i++) {
149: File file = srcFiles[i];
150: if (file.isDirectory()) {
151: if (recursive) {
152: copyFolder(file, new File(dest, file.getName()),
153: recursive, onlyNew, filter);
154: }
155: continue;
156: }
157: File destFile = new File(dest, file.getName());
158: if (onlyNew && destFile.isFile()
159: && (destFile.lastModified() > file.lastModified())) {
160: continue;
161: }
162: copyFile(file, destFile);
163: }
164: dest.setLastModified(src.lastModified());
165: }
166:
167: /**
168: * Copies streams.
169: * @param in source stream
170: * @param out destination stream
171: * @param bufferSize buffer size to use
172: * @throws IOException if any I/O error has occurred
173: */
174: public static void copyStream(final InputStream in,
175: final OutputStream out, final int bufferSize)
176: throws IOException {
177: byte[] buf = new byte[bufferSize];
178: int len;
179: while ((len = in.read(buf)) != -1) {
180: out.write(buf, 0, len);
181: }
182: }
183:
184: /**
185: * Recursively deletes whole content of the given folder.
186: * @param folder folder to be emptied
187: * @return <code>true</code> if given folder becomes empty or not exists
188: */
189: public static boolean emptyFolder(final File folder) {
190: if (!folder.isDirectory()) {
191: return true;
192: }
193: File[] files = folder.listFiles();
194: boolean result = true;
195: for (File file : files) {
196: if (file.isDirectory()) {
197: if (emptyFolder(file)) {
198: result &= file.delete();
199: } else {
200: result = false;
201: }
202: } else {
203: result &= file.delete();
204: }
205: }
206: return result;
207: }
208:
209: /**
210: * Compares two files for directories/files synchronization purposes.
211: * @param file1 one file to compare
212: * @param file2 another file to compare
213: * @return <code>true</code> if file names are equal (case sensitive), files
214: * have equal lengths and modification dates (milliseconds ignored)
215: *
216: * @see #synchronizeFolders(File, File)
217: * @see #compareFileDates(Date, Date)
218: */
219: public static boolean compareFiles(final File file1,
220: final File file2) {
221: if (!file1.isFile() || !file2.isFile()) {
222: return false;
223: }
224: if (!file1.getName().equals(file2.getName())) {
225: return false;
226: }
227: if (file1.length() != file2.length()) {
228: return false;
229: }
230: return compareFileDates(new Date(file1.lastModified()),
231: new Date(file2.lastModified()));
232: }
233:
234: /**
235: * For some reason modification milliseconds for some files are unstable,
236: * use this function to compare file dates ignoring milliseconds.
237: * @param date1 first file modification date
238: * @param date2 second file modification date
239: * @return <code>true</code> if files modification dates are equal ignoring
240: * milliseconds
241: */
242: public static boolean compareFileDates(final Date date1,
243: final Date date2) {
244: if ((date1 == null) || (date2 == null)) {
245: return false;
246: }
247: Calendar cldr = Calendar.getInstance(Locale.ENGLISH);
248: cldr.setTime(date1);
249: cldr.set(Calendar.MILLISECOND, 0);
250: long dt1 = cldr.getTimeInMillis();
251: cldr.setTime(date2);
252: cldr.set(Calendar.MILLISECOND, 0);
253: long dt2 = cldr.getTimeInMillis();
254: return dt1 == dt2;
255: }
256:
257: /**
258: * Performs one-way directories synchronization comparing files only,
259: * not folders.
260: * @param src source folder
261: * @param dest target folder
262: * @throws IOException if any I/O error has occurred
263: *
264: * @see #synchronizeFolders(File, File, FileFilter)
265: * @see #compareFiles(File, File)
266: */
267: public static void synchronizeFolders(final File src,
268: final File dest) throws IOException {
269: synchronizeFolders(src, dest, null);
270: }
271:
272: /**
273: * Performs one-way directories synchronization comparing files only,
274: * not folders.
275: * @param src source folder
276: * @param dest target folder
277: * @param filter file filter, optional, if <code>null</code> all files will
278: * be included into synchronization process
279: * @throws IOException if any I/O error has occurred
280: *
281: * @see #compareFiles(File, File)
282: */
283: public static void synchronizeFolders(final File src,
284: final File dest, final FileFilter filter)
285: throws IOException {
286: if (!src.isDirectory()) {
287: throw new IOException(ResourceManager.getMessage(
288: PACKAGE_NAME, "notAFolder", src)); //$NON-NLS-1$
289: }
290: if (dest.isFile()) {
291: throw new IOException(ResourceManager.getMessage(
292: PACKAGE_NAME, "isFile", dest)); //$NON-NLS-1$
293: }
294: if (!dest.exists() && !dest.mkdirs()) {
295: throw new IOException(ResourceManager.getMessage(
296: PACKAGE_NAME, "cantMakeFolder", dest)); //$NON-NLS-1$
297: }
298: File[] srcFiles = (filter != null) ? src.listFiles(filter)
299: : src.listFiles();
300: for (File srcFile : srcFiles) {
301: File destFile = new File(dest, srcFile.getName());
302: if (srcFile.isDirectory()) {
303: if (destFile.isFile() && !destFile.delete()) {
304: throw new IOException(ResourceManager.getMessage(
305: PACKAGE_NAME, "cantDeleteFile", destFile)); //$NON-NLS-1$
306: }
307: synchronizeFolders(srcFile, destFile, filter);
308: continue;
309: }
310: if (compareFiles(srcFile, destFile)) {
311: continue;
312: }
313: copyFile(srcFile, destFile);
314: }
315: File[] destFiles = dest.listFiles();
316: for (int i = 0; i < destFiles.length; i++) {
317: File destFile = destFiles[i];
318: File srcFile = new File(src, destFile.getName());
319: if (((filter != null) && filter.accept(destFile) && srcFile
320: .exists())
321: || ((filter == null) && srcFile.exists())) {
322: continue;
323: }
324: if (destFile.isDirectory() && !emptyFolder(destFile)) {
325: throw new IOException(ResourceManager.getMessage(
326: PACKAGE_NAME, "cantEmptyFolder", destFile)); //$NON-NLS-1$
327: }
328: if (!destFile.delete()) {
329: throw new IOException(ResourceManager.getMessage(
330: PACKAGE_NAME, "cantDeleteFile", destFile)); //$NON-NLS-1$
331: }
332: }
333: dest.setLastModified(src.lastModified());
334: }
335:
336: /**
337: * Checks if resource exist and can be opened.
338: * @param url absolute URL which points to a resource to be checked
339: * @return <code>true</code> if given URL points to an existing resource
340: */
341: public static boolean isResourceExists(final URL url) {
342: File file = url2file(url);
343: if (file != null) {
344: return file.canRead();
345: }
346: if ("jar".equalsIgnoreCase(url.getProtocol())) { //$NON-NLS-1$
347: return isJarResourceExists(url);
348: }
349: return isUrlResourceExists(url);
350: }
351:
352: private static boolean isUrlResourceExists(final URL url) {
353: try {
354: //url.openConnection().connect();
355: // Patch from Sebastian Kopsan
356: InputStream is = url.openStream();
357: try {
358: is.close();
359: } catch (IOException ioe) {
360: // ignore
361: }
362: return true;
363: } catch (IOException ioe) {
364: return false;
365: }
366: }
367:
368: private static boolean isJarResourceExists(final URL url) {
369: try {
370: String urlStr = url.toExternalForm();
371: int p = urlStr.indexOf("!/"); //$NON-NLS-1$
372: if (p == -1) {// this is invalid JAR file URL
373: return false;
374: }
375: URL fileUrl = new URL(urlStr.substring(4, p));
376: File file = url2file(fileUrl);
377: if (file == null) {// this is non-local JAR file URL
378: return isUrlResourceExists(url);
379: }
380: if (!file.canRead()) {
381: return false;
382: }
383: if (p == urlStr.length() - 2) {// URL points to the root entry of JAR file
384: return true;
385: }
386: JarFile jarFile = new JarFile(file);
387: try {
388: return jarFile.getEntry(urlStr.substring(p + 2)) != null;
389: } finally {
390: jarFile.close();
391: }
392: } catch (IOException ioe) {
393: return false;
394: }
395: }
396:
397: /**
398: * Opens input stream for given resource. This method behaves differently
399: * for different URL types:
400: * <ul>
401: * <li>for <b>local files</b> it returns buffered file input stream;</li>
402: * <li>for <b>local JAR files</b> it reads resource content into memory
403: * buffer and returns byte array input stream that wraps those
404: * buffer (this prevents locking JAR file);</li>
405: * <li>for <b>common URL's</b> this method simply opens stream to that URL
406: * using standard URL API.</li>
407: * </ul>
408: * It is not recommended to use this method for big resources within JAR
409: * files.
410: * @param url resource URL
411: * @return input stream for given resource
412: * @throws IOException if any I/O error has occurred
413: */
414: public static InputStream getResourceInputStream(final URL url)
415: throws IOException {
416: File file = url2file(url);
417: if (file != null) {
418: return new BufferedInputStream(new FileInputStream(file));
419: }
420: if (!"jar".equalsIgnoreCase(url.getProtocol())) { //$NON-NLS-1$
421: return url.openStream();
422: }
423: String urlStr = url.toExternalForm();
424: if (urlStr.endsWith("!/")) { //$NON-NLS-1$
425: //JAR URL points to a root entry
426: throw new FileNotFoundException(url.toExternalForm());
427: }
428: int p = urlStr.indexOf("!/"); //$NON-NLS-1$
429: if (p == -1) {
430: throw new MalformedURLException(url.toExternalForm());
431: }
432: String path = urlStr.substring(p + 2);
433: file = url2file(new URL(urlStr.substring(4, p)));
434: if (file == null) {// non-local JAR file URL
435: return url.openStream();
436: }
437: JarFile jarFile = new JarFile(file);
438: try {
439: ZipEntry entry = jarFile.getEntry(path);
440: if (entry == null) {
441: throw new FileNotFoundException(url.toExternalForm());
442: }
443: InputStream in = jarFile.getInputStream(entry);
444: try {
445: ByteArrayOutputStream out = new ByteArrayOutputStream();
446: copyStream(in, out, 1024);
447: return new ByteArrayInputStream(out.toByteArray());
448: } finally {
449: in.close();
450: }
451: } finally {
452: jarFile.close();
453: }
454: }
455:
456: /**
457: * Utility method to convert local URL to a {@link File} object.
458: * @param url an URL
459: * @return file object for given URL or <code>null</code> if URL is not
460: * local
461: */
462: @SuppressWarnings("deprecation")
463: public static File url2file(final URL url) {
464: String prot = url.getProtocol();
465: if ("jar".equalsIgnoreCase(prot)) { //$NON-NLS-1$
466: if (url.getFile().endsWith("!/")) { //$NON-NLS-1$
467: String urlStr = url.toExternalForm();
468: try {
469: return url2file(new URL(urlStr.substring(4, urlStr
470: .length() - 2)));
471: } catch (MalformedURLException mue) {
472: // ignore
473: }
474: }
475: return null;
476: }
477: if (!"file".equalsIgnoreCase(prot)) { //$NON-NLS-1$
478: return null;
479: }
480: try {
481: // Method URL.toURI() may produce URISyntaxException for some
482: // "valid" URL's that contain spaces or other "illegal" characters.
483: //return new File(url.toURI());
484: return new File(URLDecoder.decode(url.getFile(), "UTF-8")); //$NON-NLS-1$
485: } catch (UnsupportedEncodingException e) {
486: return new File(URLDecoder.decode(url.getFile()));
487: }
488: }
489:
490: /**
491: * Utility method to convert a {@link File} object to a local URL.
492: * @param file a file object
493: * @return absolute URL that points to the given file
494: * @throws MalformedURLException if file can't be represented as URL for
495: * some reason
496: */
497: public static URL file2url(final File file)
498: throws MalformedURLException {
499: try {
500: return file.getCanonicalFile().toURI().toURL();
501: } catch (MalformedURLException mue) {
502: throw mue;
503: } catch (IOException ioe) {
504: throw new MalformedURLException(ResourceManager.getMessage(
505: PACKAGE_NAME, "file2urlFailed", //$NON-NLS-1$
506: new Object[] { file, ioe }));
507: }
508: }
509:
510: private IoUtil() {
511: // no-op
512: }
513: }
|