blob: 52b3015558f33df21a569dafdf9d2c277ec6c816 [file] [log] [blame]
/*
* ******************************************************************************
* Copyright (C) 2007-2008, International Business Machines Corporation and others.
* All Rights Reserved.
* ******************************************************************************
*/
package com.ibm.icu.dev.tool.tzu;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
/**
* A class that represents an updatable ICU4J jar file. A file is an updatable ICU4J jar file if it
* <ul>
* <li>exists</li>
* <li>is a file (ie. not a directory)</li>
* <li>does not end with .ear or .war (these file types are unsupported)</li>
* <li>ends with .jar</li>
* <li>is updatable according the <code>isUpdatable</code></li>
* <li>is not signed.</li>
* </ul>
*/
public class ICUFile {
/**
* ICU version to use if one cannot be found.
*/
public static final String ICU_VERSION_UNKNOWN = "Unknown";
/**
* A directory entry that is found in every updatable ICU4J jar file.
*/
public static final String TZ_ENTRY_DIR = "com/ibm/icu/impl";
/**
* The timezone resource filename.
*/
public static final String TZ_ENTRY_FILENAME = "zoneinfo.res";
/**
* The metazone resource filename.
*/
private static final String MZ_ENTRY_FILENAME = "metazoneInfo.res";
/**
* Key to use when getting the version of a timezone resource.
*/
public static final String TZ_VERSION_KEY = "TZVersion";
/**
* Timezone version to use if one cannot be found.
*/
public static final String TZ_VERSION_UNKNOWN = "Unknown";
/**
* Jar entry path where some files are duplicated. This is an issue with icu4j 3.8.0.
*/
public static final String DUPLICATE_ENTRY_PATH = "com/ibm/icu/impl/duration/impl/data";
/**
* The buffer size to use for copying data.
*/
private static final int BUFFER_SIZE = 1024;
/**
* A map that caches links from URLs to time zone data to their downloaded File counterparts.
*/
private static final Map cacheMap = new HashMap();
/**
* Determines the version of a timezone resource as a standard file without locking the file.
*
* @param tzFile
* The file representing the timezone resource.
* @param logger
* The current logger.
* @return The version of the timezone resource.
*/
public static String findFileTZVersion(File tzFile, Logger logger) {
ICUFile rawTZFile = new ICUFile(logger);
try {
File temp = File.createTempFile("zoneinfo", ".res");
temp.deleteOnExit();
rawTZFile.copyFile(tzFile, temp);
return findTZVersion(temp, logger);
} catch (IOException ex) {
logger.errorln(ex.getMessage());
return null;
}
}
/**
* Determines the version of a timezone resource as a standard file, but locks the file for the
* duration of the program.
*
* @param tzFile
* The file representing the timezone resource.
* @param logger
* The current logger.
* @return The version of the timezone resource.
*/
private static String findTZVersion(File tzFile, Logger logger) {
try {
String filename = tzFile.getName();
String entryname = filename.substring(0, filename.length() - ".res".length());
URL url = new URL(tzFile.getAbsoluteFile().getParentFile().toURL().toString());
ClassLoader loader = new URLClassLoader(new URL[] { url });
// UResourceBundle bundle = UResourceBundle.getBundleInstance("",
// entryname, loader);
URL bundleURL = new URL(new File("icu4j.jar").toURL().toString());
URLClassLoader bundleLoader = new URLClassLoader(new URL[] { bundleURL });
Class bundleClass = bundleLoader.loadClass("com.ibm.icu.util.UResourceBundle");
Method bundleGetInstance = bundleClass.getMethod("getBundleInstance", new Class[] {
String.class, String.class, ClassLoader.class });
Object bundle = bundleGetInstance.invoke(null, new Object[] { "", entryname, loader });
if (bundle != null) {
Method bundleGetString = bundleClass.getMethod("getString",
new Class[] { String.class });
String tzVersion = (String) bundleGetString.invoke(bundle,
new Object[] { TZ_VERSION_KEY });
if (tzVersion != null)
return tzVersion;
}
} catch (MalformedURLException ex) {
// this should never happen
logger.errorln("Internal program error.");
logger.logStackTraceToBoth(ex);
} catch (ClassNotFoundException ex) {
// this would most likely happen when UResourceBundle cannot be
// resolved, which is when icu4j.jar is not where it should be
logger.errorln("icu4j.jar not found");
logger.logStackTraceToBoth(ex);
} catch (NoSuchMethodException ex) {
// this can only be caused by a very unlikely scenario
logger.errorln("icu4j.jar not correct");
logger.logStackTraceToBoth(ex);
} catch (IllegalAccessException ex) {
// this can only be caused by a very unlikely scenario
logger.errorln("icu4j.jar not correct");
logger.logStackTraceToBoth(ex);
} catch (InvocationTargetException ex) {
// if this is holding a MissingResourceException, then this is not
// an error -- some zoneinfo files are missing version numbers
if (!(ex.getTargetException() instanceof MissingResourceException)) {
logger.errorln("icu4j.jar not correct");
logger.logStackTraceToBoth(ex);
}
}
return TZ_VERSION_UNKNOWN;
}
/**
* Finds the jar entry in the jar file that represents a timezone resource and returns it, or
* null if none is found.
*
* @param jar The jar file to search.
* @param entryName The target entry name
*
* @return The jar entry representing the timezone resource in the jar file, or null if none is
* found.
*/
private static JarEntry getTZEntry(JarFile jar, String entryName) {
JarEntry tzEntry = null;
Enumeration e = jar.entries();
while (e.hasMoreElements()) {
tzEntry = (JarEntry) e.nextElement();
if (tzEntry.getName().endsWith(entryName))
return tzEntry;
}
return null;
}
/**
* The ICU4J jar file represented by this ICUFile.
*/
private File icuFile;
/**
* The ICU version of the ICU4J jar.
*/
private String icuVersion;
/**
* The current logger.
*/
private Logger logger;
/**
* The entry for the timezone resource inside the ICU4J jar.
*/
private JarEntry tzEntry;
/**
* The entry for the metazone resource inside the ICU4J jar.
*/
private JarEntry mzEntry;
/**
* The version of the timezone resource inside the ICU4J jar.
*/
private String tzVersion;
/**
* Constructs an ICUFile around a file. See <code>initialize</code> for details.
*
* @param file
* The file to wrap this ICUFile around.
* @param logger
* The current logger.
* @throws IOException
*/
public ICUFile(File file, Logger logger) throws IOException {
initialize(file, logger);
}
/**
* Constructs an ICUFile around a file. See <code>initialize</code> for details.
*
* @param filename
* The file to wrap this ICUFile around.
* @param logger
* The current logger.
* @throws IOException
*/
public ICUFile(String filename, Logger logger) throws IOException {
if (filename == null || filename.trim().length() == 0)
throw new IOException("cannot be blank");
initialize(new File(filename), logger);
}
/**
* Constructs a blank ICUFile. Used internally for timezone resource files that are not
* contained within a jar.
*
* @param logger
* The current logger.
*/
private ICUFile(Logger logger) {
this.logger = logger;
}
/**
* Compares two ICUFiles by the file they represent.
*
* @param other
* The other ICUFile to compare to.
* @return Whether the files represented by the two ICUFiles are equal.
*/
public boolean equals(Object other) {
return (!(other instanceof ICUFile)) ? false : icuFile.getAbsoluteFile().equals(
((ICUFile) other).icuFile.getAbsoluteFile());
}
/**
* Determines the version of a timezone resource in a jar file without locking the jar file.
*
* @return The version of the timezone resource.
*/
public String findEntryTZVersion() {
try {
File temp = File.createTempFile("zoneinfo", ".res");
temp.deleteOnExit();
copyEntry(icuFile, tzEntry, temp);
return findTZVersion(temp, logger);
} catch (IOException ex) {
logger.errorln(ex.getMessage());
return null;
}
}
/**
* Returns the File object represented by this ICUFile object.
*
* @return The File object represented by this ICUFile object.
*/
public File getFile() {
return icuFile;
}
/**
* Returns the filename of this ICUFile object, without the path.
*
* @return The filename of this ICUFile object, without the path.
*/
public String getFilename() {
return icuFile.getName();
}
/**
* Returns the ICU version of this ICU4J jar.
*
* @return The ICU version of this ICU4J jar.
*/
public String getICUVersion() {
return icuVersion;
}
/**
* Returns the path of this ICUFile object, without the filename.
*
* @return The path of this ICUFile object, without the filename.
*/
public String getPath() {
return icuFile.getAbsoluteFile().getParent();
}
// public static String findURLTZVersion(File tzFile) {
// try {
// File temp = File.createTempFile("zoneinfo", ".res");
// temp.deleteOnExit();
// copyFile(tzFile, temp);
// return findTZVersion(temp);
// } catch (IOException ex) {
// ex.printStackTrace();
// return null;
// }
// }
/**
* Returns the timezone resource version.
*
* @return The timezone resource version.
*/
public String getTZVersion() {
return tzVersion;
}
/**
* Returns the result of getFile().toString().
*
* @return The result of getFile().toString().
*/
public String toString() {
return getFile().toString();
}
/**
* Updates the timezone resource in this ICUFile using <code>insertURL</code> as the source of
* the new timezone resource and the backup directory <code>backupDir</code> to store a copy
* of the ICUFile.
*
* @param insertURL
* The url location of the timezone resource to use.
* @param backupDir
* The directory to store a backup for this ICUFile, or null if no backup.
* @throws IOException
* @throws InterruptedException
*/
public void update(URL insertURL, File backupDir) throws IOException, InterruptedException {
String message = "Updating " + icuFile.getPath() + " ...";
logger.printlnToBoth("");
logger.printlnToBoth(message);
if (!icuFile.canRead() || !icuFile.canWrite())
throw new IOException("Missing permissions for " + icuFile.getPath());
JarEntry[] jarEntries = new JarEntry[2];
URL[] insertURLs = new URL[2];
jarEntries[0] = tzEntry;
insertURLs[0] = getCachedURL(insertURL);
if (insertURLs[0] == null)
throw new IOException(
"Could not download the Time Zone data, skipping update for this jar.");
// Check if metazoneInfo.res is available
String tzURLStr = insertURL.toString();
int lastSlashIdx = tzURLStr.lastIndexOf('/');
if (lastSlashIdx >= 0) {
String mzURLStr = tzURLStr.substring(0, lastSlashIdx + 1) + MZ_ENTRY_FILENAME;
insertURLs[1] = getCachedURL(new URL(mzURLStr));
if (insertURLs[1] != null) {
jarEntries[1] = mzEntry;
}
}
File backupFile = null;
if ((backupFile = createBackupFile(icuFile, backupDir)) == null)
throw new IOException(
"Could not create an empty backup file (the original jar file remains unchanged).");
if (!copyFile(icuFile, backupFile))
throw new IOException(
"Could not copy the original jar file to the backup location (the original jar file remains unchanged).");
logger.printlnToBoth("Backup location: " + backupFile.getPath());
if (!createUpdatedJar(backupFile, icuFile, jarEntries, insertURLs))
throw new IOException(
"Could not create an updated jar file at the original location (the original jar file is at the backup location).");
// get the new timezone resource version
tzVersion = findEntryTZVersion();
message = "Successfully updated " + icuFile.getPath();
logger.printlnToBoth(message);
}
/**
* Copies the jar entry <code>insertEntry</code> in <code>inputFile</code> to
* <code>outputFile</code>.
*
* @param inputFile
* The jar file containing <code>insertEntry</code>.
* @param inputEntry
* The entry to copy.
* @param outputFile
* The output file.
* @return Whether the operation was successful.
*/
private boolean copyEntry(File inputFile, JarEntry inputEntry, File outputFile) {
logger.loglnToBoth("Copying from " + inputFile + "!/" + inputEntry + " to " + outputFile
+ ".");
JarFile jar = null;
InputStream istream = null;
OutputStream ostream = null;
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
boolean success = false;
try {
jar = new JarFile(inputFile);
istream = jar.getInputStream(inputEntry);
ostream = new FileOutputStream(outputFile);
while ((bytesRead = istream.read(buffer)) != -1)
ostream.write(buffer, 0, bytesRead);
success = true;
logger.loglnToBoth("Copy successful.");
} catch (IOException ex) {
outputFile.delete();
logger.loglnToBoth("Copy failed.");
logger.logStackTraceToBoth(ex);
} finally {
// safely close the streams
tryClose(jar);
tryClose(istream);
tryClose(ostream);
}
return success;
}
/**
* Copies <code>inputFile</code> to <code>outputFile</code>.
*
* @param inputFile
* The input file.
* @param outputFile
* The output file.
* @return Whether the operation was successful.
*/
private boolean copyFile(File inputFile, File outputFile) {
logger.loglnToBoth("Copying from " + inputFile + " to " + outputFile + ".");
InputStream istream = null;
OutputStream ostream = null;
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
boolean success = false;
try {
istream = new FileInputStream(inputFile);
ostream = new FileOutputStream(outputFile);
while ((bytesRead = istream.read(buffer)) != -1)
ostream.write(buffer, 0, bytesRead);
success = true;
logger.loglnToBoth("Copy successful.");
} catch (IOException ex) {
outputFile.delete();
logger.loglnToBoth("Copy failed.");
logger.logStackTraceToBoth(ex);
} finally {
// safely close the streams
tryClose(istream);
tryClose(ostream);
}
return success;
}
/**
* Creates a temporary file for the jar file <code>inputFile</code> under the directory
* <code>backupBase</code> and returns it, or returns null if a temporary file could not be
* created. Does not put any data in the newly created file yet.
*
* @param inputFile
* The file to backup.
* @param backupBase
* The directory where backups are to be stored.
* @return The temporary file that was created.
*/
private File createBackupFile(File inputFile, File backupBase) {
logger.loglnToBoth("Creating backup file for " + inputFile + " at " + backupBase + ".");
String filename = inputFile.getName();
String suffix = ".jar";
String prefix = filename.substring(0, filename.length() - suffix.length());
if (backupBase == null) {
try {
// no backup directory means we need to create a temporary file
// that will be deleted on exit
File backupFile = File.createTempFile(prefix + "~", suffix);
backupFile.deleteOnExit();
return backupFile;
} catch (IOException ex) {
return null;
}
}
File backupFile = null;
File backupDesc = null;
File backupDir = new File(backupBase.getPath(), prefix);
PrintStream ostream = null;
try {
backupBase.mkdir();
backupDir.mkdir();
backupFile = File.createTempFile(prefix + "~", suffix, backupDir);
backupDesc = new File(backupDir.getPath(), backupFile.getName().substring(0,
backupFile.getName().length() - suffix.length())
+ ".txt");
backupDesc.createNewFile();
ostream = new PrintStream(new FileOutputStream(backupDesc));
ostream.println(inputFile.getPath());
logger.loglnToBoth("Successfully created backup file at " + backupFile + ".");
} catch (IOException ex) {
logger.loglnToBoth("Failed to create backup file.");
logger.logStackTraceToBoth(ex);
if (backupFile != null)
backupFile.delete();
if (backupDesc != null)
backupDesc.delete();
backupDir.delete();
backupFile = null;
} finally {
tryClose(ostream);
}
return backupFile;
}
/**
* Copies <code>inputFile</code> to <code>outputFile</code>, replacing
* <code>insertEntry</code> with <code>inputURL</code>.
*
* @param inputFile
* The input jar file.
* @param outputFile
* The output jar file.
* @param insertEntry
* The entry to be replaced.
* @param inputURL
* The URL to use in replacing the entry.
* @return Whether the operation was successful.
*/
private boolean createUpdatedJar(File inputFile, File outputFile, JarEntry[] insertEntries,
URL[] inputURLs) {
logger.loglnToBoth("Copying " + inputFile + " to " + outputFile + ",");
for (int i = 0; i < insertEntries.length; i++) {
if (insertEntries[i] != null) {
logger.loglnToBoth(" replacing " + insertEntries[i] + " with " + inputURLs[i]);
}
}
JarFile jar = null;
JarOutputStream ostream = null;
InputStream istream = null;
InputStream jstream = null;
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
boolean success = false;
Set possibleDuplicates = new HashSet();
try {
jar = new JarFile(inputFile);
ostream = new JarOutputStream(new FileOutputStream(outputFile));
Enumeration e = jar.entries();
while (e.hasMoreElements()) {
JarEntry currentEntry = (JarEntry) e.nextElement();
String entryName = currentEntry.getName();
if (entryName.startsWith(DUPLICATE_ENTRY_PATH)) {
if (!possibleDuplicates.contains(entryName)) {
possibleDuplicates.add(entryName);
} else {
// ruh roh, we have a duplicate entry!
// (just ignore it and continue)
logger.printlnToBoth("Warning: Duplicate " + entryName
+ " found. Ignoring the duplicate.");
continue;
}
}
boolean isReplaced = false;
for (int i = 0; i < insertEntries.length; i++) {
if (insertEntries[i] != null) {
if (entryName.equals(insertEntries[i].getName())) {
// if the current entry *is* the one that needs updating write a new entry based
// on the input stream (from the URL)
// currentEntry.setTime(System.currentTimeMillis());
ostream.putNextEntry(new JarEntry(entryName));
URLConnection con = inputURLs[i].openConnection();
con.setRequestProperty("user-agent", System.getProperty("http.agent"));
istream = con.getInputStream();
while ((bytesRead = istream.read(buffer)) != -1) {
ostream.write(buffer, 0, bytesRead);
}
istream.close();
isReplaced = true;
break;
}
}
}
if (!isReplaced) {
// if the current entry isn't the one that needs updating write a copy of the
// old entry from the old file
ostream.putNextEntry(new JarEntry(entryName));
jstream = jar.getInputStream(currentEntry);
while ((bytesRead = jstream.read(buffer)) != -1)
ostream.write(buffer, 0, bytesRead);
jstream.close();
}
}
success = true;
logger.loglnToBoth("Copy successful.");
} catch (IOException ex) {
outputFile.delete();
logger.loglnToBoth("Copy failed:");
logger.logStackTraceToBoth(ex);
} finally {
// safely close the streams
tryClose(istream);
tryClose(ostream);
tryClose(jstream);
tryClose(jar);
}
return success;
}
/**
* Performs the shared work of the constructors. Throws an IOException if <code>file</code>...
* <ul>
* <li>does not exist</li>
* <li>is not a file</li>
* <li>ends with .ear or .war (these file types are unsupported)</li>
* <li>does not end with .jar</li>
* <li>is not updatable according the <code>isUpdatable</code></li>
* <li>is signed.</li>
* </ul>
* If an exception is not thrown, the ICUFile is fully initialized.
*
* @param file
* The file to wrap this ICUFile around.
* @param logger
* The current logger.
* @throws IOException
*/
private void initialize(File file, Logger log) throws IOException {
this.icuFile = file;
this.logger = log;
String message = null;
if (!file.exists()) {
message = "Skipped " + file.getPath() + " (does not exist).";
} else if (!file.isFile()) {
message = "Skipped " + file.getPath() + " (not a file).";
} else if (file.getName().endsWith(".ear") || file.getName().endsWith(".war")) {
message = "Skipped " + file.getPath()
+ " (this tool does not support .ear and .war files).";
logger.loglnToBoth(message);
} else if (!file.canRead() || !file.canWrite()) {
message = "Skipped " + file.getPath() + " (missing permissions).";
} else if (!file.getName().endsWith(".jar")) {
message = "Skipped " + file.getPath() + " (not a jar file).";
} else if (!isUpdatable()) {
message = "Skipped " + file.getPath() + " (not an updatable ICU4J jar).";
} else if (isSigned()) {
message = "Skipped " + file.getPath() + " (cannot update signed jars).";
logger.loglnToBoth(message);
} else if (isEclipseFragment()) {
message = "Skipped " + file.getPath()
+ " (eclipse fragments must be updated through ICU).";
logger.loglnToBoth(message);
}
if (message != null)
throw new IOException(message);
tzVersion = findEntryTZVersion();
}
/**
* Determines whether the current jar is an Eclipse Data Fragment.
*
* @return Whether the current jar is an Eclipse Fragment.
*/
private boolean isEclipseDataFragment() {
return (icuFile.getPath().indexOf("plugins" + File.separator + "com.ibm.icu.data.update") >= 0 && icuFile
.getName().equalsIgnoreCase("icu-data.jar"));
}
/**
* Determines whether the current jar is an Eclipse Fragment.
*
* @return Whether the current jar is an Eclipse Fragment.
*/
private boolean isEclipseFragment() {
return (isEclipseDataFragment() || isEclipseMainFragment());
}
/**
* Determines whether the current jar is an Eclipse Main Fragment.
*
* @return Whether the current jar is an Eclipse Fragment.
*/
private boolean isEclipseMainFragment() {
return (icuFile.getPath().indexOf("plugins") >= 0 && icuFile.getName().startsWith(
"com.ibm.icu_"));
}
/**
* Determines whether a timezone resource in a jar file is signed.
*
* @return Whether a timezone resource in a jar file is signed.
*/
private boolean isSigned() {
return tzEntry.getCertificates() != null;
}
/**
* Gathers information on the jar file represented by this ICUFile object and returns whether it
* is an updatable ICU4J jar file.
*
* @return Whether the jar file represented by this ICUFile object is an updatable ICU4J jar
* file.
*/
private boolean isUpdatable() {
JarFile jar = null;
boolean success = false;
try {
// open icuFile as a jar file
jar = new JarFile(icuFile);
// get its manifest to determine the ICU version
Manifest manifest = jar.getManifest();
icuVersion = ICU_VERSION_UNKNOWN;
if (manifest != null) {
Iterator iter = manifest.getEntries().values().iterator();
while (iter.hasNext()) {
Attributes attr = (Attributes) iter.next();
String ver = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
if (ver != null) {
icuVersion = ver;
break;
}
}
}
// if the jar's directory structure contains TZ_ENTRY_DIR and there
// is a timezone resource in the jar, then the jar is updatable
success = (jar.getJarEntry(TZ_ENTRY_DIR) != null)
&& ((this.tzEntry = getTZEntry(jar, TZ_ENTRY_FILENAME)) != null);
// if the jar file contains metazoneInfo.res, initialize mzEntry -
// this is true for ICU4J 3.8.1 or later releases
if (success) {
mzEntry = getTZEntry(jar, MZ_ENTRY_FILENAME);
}
} catch (IOException ex) {
// unable to create the JarFile or unable to get the Manifest
// log the unexplained i/o error, but we must drudge on
logger.loglnToBoth("Error reading " + icuFile.getPath() + ".");
logger.logStackTraceToBoth(ex);
} finally {
// close the jar gracefully
if (!tryClose(jar))
logger.errorln("Could not properly close the jar file " + icuFile + ".");
}
// return whether the jar is updatable or not
return success;
}
private URL getCachedURL(URL url) {
File outputFile = (File) cacheMap.get(url);
if (outputFile != null) {
try {
return outputFile.toURL();
} catch (MalformedURLException ex) {
return null;
}
} else {
InputStream istream = null;
OutputStream ostream = null;
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
boolean success = false;
try {
String urlStr = url.toString();
int lastSlash = urlStr.lastIndexOf('/');
String fileName = lastSlash >= 0 ? urlStr.substring(lastSlash + 1) : urlStr;
outputFile = File.createTempFile(fileName, null);
outputFile.deleteOnExit();
logger.loglnToBoth("Downloading from " + url + " to " + outputFile.getPath() + ".");
URLConnection con = url.openConnection();
con.setRequestProperty("user-agent", System.getProperty("http.agent"));
istream = con.getInputStream();
ostream = new FileOutputStream(outputFile);
while ((bytesRead = istream.read(buffer)) != -1)
ostream.write(buffer, 0, bytesRead);
success = true;
logger.loglnToBoth("Download successful.");
} catch (IOException ex) {
outputFile.delete();
logger.loglnToBoth("Download failed.");
logger.logStackTraceToBoth(ex);
} finally {
// safely close the streams
tryClose(istream);
tryClose(ostream);
}
try {
return (success && outputFile != null) ? outputFile.toURL() : null;
} catch (MalformedURLException ex) {
return null;
}
}
}
/**
* Tries to close <code>closeable</code> if possible.
*
* @param closeable
* A closeable object
* @return false if an IOException occured, true otherwise.
*/
private boolean tryClose(InputStream closeable) {
if (closeable != null)
try {
closeable.close();
return true;
} catch (IOException ex) {
return false;
}
else
return true;
}
/**
* Tries to close <code>closeable</code> if possible.
*
* @param closeable
* A closeable object
* @return false if an IOException occured, true otherwise.
*/
private boolean tryClose(OutputStream closeable) {
if (closeable != null)
try {
closeable.close();
return true;
} catch (IOException ex) {
return false;
}
else
return true;
}
/**
* Tries to close <code>closeable</code> if possible.
*
* @param closeable
* A closeable object
* @return false if an IOException occured, true otherwise.
*/
private boolean tryClose(JarFile closeable) {
if (closeable != null)
try {
closeable.close();
return true;
} catch (IOException ex) {
return false;
}
else
return true;
}
}