2 * ******************************************************************************
\r
3 * Copyright (C) 2007-2008, International Business Machines Corporation and others.
\r
4 * All Rights Reserved.
\r
5 * ******************************************************************************
\r
7 package com.ibm.icu.dev.tool.tzu;
\r
10 import java.io.FileInputStream;
\r
11 import java.io.FileOutputStream;
\r
12 import java.io.IOException;
\r
13 import java.io.InputStream;
\r
14 import java.io.OutputStream;
\r
15 import java.io.PrintStream;
\r
16 import java.lang.reflect.InvocationTargetException;
\r
17 import java.lang.reflect.Method;
\r
18 import java.net.MalformedURLException;
\r
19 import java.net.URL;
\r
20 import java.net.URLClassLoader;
\r
21 import java.net.URLConnection;
\r
22 import java.util.Enumeration;
\r
23 import java.util.HashMap;
\r
24 import java.util.HashSet;
\r
25 import java.util.Iterator;
\r
26 import java.util.Map;
\r
27 import java.util.MissingResourceException;
\r
28 import java.util.Set;
\r
29 import java.util.jar.Attributes;
\r
30 import java.util.jar.JarEntry;
\r
31 import java.util.jar.JarFile;
\r
32 import java.util.jar.JarOutputStream;
\r
33 import java.util.jar.Manifest;
\r
36 * A class that represents an updatable ICU4J jar file. A file is an updatable ICU4J jar file if it
\r
39 * <li>is a file (ie. not a directory)</li>
\r
40 * <li>does not end with .ear or .war (these file types are unsupported)</li>
\r
41 * <li>ends with .jar</li>
\r
42 * <li>is updatable according the <code>isUpdatable</code></li>
\r
43 * <li>is not signed.</li>
\r
46 public class ICUFile {
\r
48 * ICU version to use if one cannot be found.
\r
50 public static final String ICU_VERSION_UNKNOWN = "Unknown";
\r
53 * A directory entry that is found in every updatable ICU4J jar file.
\r
55 public static final String TZ_ENTRY_DIR = "com/ibm/icu/impl";
\r
58 * The timezone resource filename.
\r
60 public static final String TZ_ENTRY_FILENAME = "zoneinfo.res";
\r
63 * The metazone resource filename.
\r
65 private static final String MZ_ENTRY_FILENAME = "metazoneInfo.res";
\r
68 * Key to use when getting the version of a timezone resource.
\r
70 public static final String TZ_VERSION_KEY = "TZVersion";
\r
73 * Timezone version to use if one cannot be found.
\r
75 public static final String TZ_VERSION_UNKNOWN = "Unknown";
\r
78 * Jar entry path where some files are duplicated. This is an issue with icu4j 3.8.0.
\r
80 public static final String DUPLICATE_ENTRY_PATH = "com/ibm/icu/impl/duration/impl/data";
\r
83 * The buffer size to use for copying data.
\r
85 private static final int BUFFER_SIZE = 1024;
\r
88 * A map that caches links from URLs to time zone data to their downloaded File counterparts.
\r
90 private static final Map cacheMap = new HashMap();
\r
93 * Determines the version of a timezone resource as a standard file without locking the file.
\r
96 * The file representing the timezone resource.
\r
98 * The current logger.
\r
99 * @return The version of the timezone resource.
\r
101 public static String findFileTZVersion(File tzFile, Logger logger) {
\r
102 ICUFile rawTZFile = new ICUFile(logger);
\r
105 File temp = File.createTempFile("zoneinfo", ".res");
\r
106 temp.deleteOnExit();
\r
107 rawTZFile.copyFile(tzFile, temp);
\r
108 return findTZVersion(temp, logger);
\r
109 } catch (IOException ex) {
\r
110 logger.errorln(ex.getMessage());
\r
116 * Determines the version of a timezone resource as a standard file, but locks the file for the
\r
117 * duration of the program.
\r
120 * The file representing the timezone resource.
\r
122 * The current logger.
\r
123 * @return The version of the timezone resource.
\r
125 private static String findTZVersion(File tzFile, Logger logger) {
\r
127 String filename = tzFile.getName();
\r
128 String entryname = filename.substring(0, filename.length() - ".res".length());
\r
130 URL url = new URL(tzFile.getAbsoluteFile().getParentFile().toURL().toString());
\r
131 ClassLoader loader = new URLClassLoader(new URL[] { url });
\r
133 // UResourceBundle bundle = UResourceBundle.getBundleInstance("",
\r
134 // entryname, loader);
\r
136 URL bundleURL = new URL(new File("icu4j.jar").toURL().toString());
\r
137 URLClassLoader bundleLoader = new URLClassLoader(new URL[] { bundleURL });
\r
138 Class bundleClass = bundleLoader.loadClass("com.ibm.icu.util.UResourceBundle");
\r
139 Method bundleGetInstance = bundleClass.getMethod("getBundleInstance", new Class[] {
\r
140 String.class, String.class, ClassLoader.class });
\r
141 Object bundle = bundleGetInstance.invoke(null, new Object[] { "", entryname, loader });
\r
143 if (bundle != null) {
\r
144 Method bundleGetString = bundleClass.getMethod("getString",
\r
145 new Class[] { String.class });
\r
146 String tzVersion = (String) bundleGetString.invoke(bundle,
\r
147 new Object[] { TZ_VERSION_KEY });
\r
148 if (tzVersion != null)
\r
151 } catch (MalformedURLException ex) {
\r
152 // this should never happen
\r
153 logger.errorln("Internal program error.");
\r
154 logger.logStackTraceToBoth(ex);
\r
155 } catch (ClassNotFoundException ex) {
\r
156 // this would most likely happen when UResourceBundle cannot be
\r
157 // resolved, which is when icu4j.jar is not where it should be
\r
158 logger.errorln("icu4j.jar not found");
\r
159 logger.logStackTraceToBoth(ex);
\r
160 } catch (NoSuchMethodException ex) {
\r
161 // this can only be caused by a very unlikely scenario
\r
162 logger.errorln("icu4j.jar not correct");
\r
163 logger.logStackTraceToBoth(ex);
\r
164 } catch (IllegalAccessException ex) {
\r
165 // this can only be caused by a very unlikely scenario
\r
166 logger.errorln("icu4j.jar not correct");
\r
167 logger.logStackTraceToBoth(ex);
\r
168 } catch (InvocationTargetException ex) {
\r
169 // if this is holding a MissingResourceException, then this is not
\r
170 // an error -- some zoneinfo files are missing version numbers
\r
171 if (!(ex.getTargetException() instanceof MissingResourceException)) {
\r
172 logger.errorln("icu4j.jar not correct");
\r
173 logger.logStackTraceToBoth(ex);
\r
177 return TZ_VERSION_UNKNOWN;
\r
181 * Finds the jar entry in the jar file that represents a timezone resource and returns it, or
\r
182 * null if none is found.
\r
184 * @param jar The jar file to search.
\r
185 * @param entryName The target entry name
\r
187 * @return The jar entry representing the timezone resource in the jar file, or null if none is
\r
190 private static JarEntry getTZEntry(JarFile jar, String entryName) {
\r
191 JarEntry tzEntry = null;
\r
192 Enumeration e = jar.entries();
\r
193 while (e.hasMoreElements()) {
\r
194 tzEntry = (JarEntry) e.nextElement();
\r
195 if (tzEntry.getName().endsWith(entryName))
\r
202 * The ICU4J jar file represented by this ICUFile.
\r
204 private File icuFile;
\r
207 * The ICU version of the ICU4J jar.
\r
209 private String icuVersion;
\r
212 * The current logger.
\r
214 private Logger logger;
\r
217 * The entry for the timezone resource inside the ICU4J jar.
\r
219 private JarEntry tzEntry;
\r
222 * The entry for the metazone resource inside the ICU4J jar.
\r
224 private JarEntry mzEntry;
\r
227 * The version of the timezone resource inside the ICU4J jar.
\r
229 private String tzVersion;
\r
232 * Constructs an ICUFile around a file. See <code>initialize</code> for details.
\r
235 * The file to wrap this ICUFile around.
\r
237 * The current logger.
\r
238 * @throws IOException
\r
240 public ICUFile(File file, Logger logger) throws IOException {
\r
241 initialize(file, logger);
\r
245 * Constructs an ICUFile around a file. See <code>initialize</code> for details.
\r
248 * The file to wrap this ICUFile around.
\r
250 * The current logger.
\r
251 * @throws IOException
\r
253 public ICUFile(String filename, Logger logger) throws IOException {
\r
254 if (filename == null || filename.trim().length() == 0)
\r
255 throw new IOException("cannot be blank");
\r
257 initialize(new File(filename), logger);
\r
261 * Constructs a blank ICUFile. Used internally for timezone resource files that are not
\r
262 * contained within a jar.
\r
265 * The current logger.
\r
267 private ICUFile(Logger logger) {
\r
268 this.logger = logger;
\r
272 * Compares two ICUFiles by the file they represent.
\r
275 * The other ICUFile to compare to.
\r
276 * @return Whether the files represented by the two ICUFiles are equal.
\r
278 public boolean equals(Object other) {
\r
279 return (!(other instanceof ICUFile)) ? false : icuFile.getAbsoluteFile().equals(
\r
280 ((ICUFile) other).icuFile.getAbsoluteFile());
\r
284 * Determines the version of a timezone resource in a jar file without locking the jar file.
\r
286 * @return The version of the timezone resource.
\r
288 public String findEntryTZVersion() {
\r
290 File temp = File.createTempFile("zoneinfo", ".res");
\r
291 temp.deleteOnExit();
\r
292 copyEntry(icuFile, tzEntry, temp);
\r
293 return findTZVersion(temp, logger);
\r
294 } catch (IOException ex) {
\r
295 logger.errorln(ex.getMessage());
\r
301 * Returns the File object represented by this ICUFile object.
\r
303 * @return The File object represented by this ICUFile object.
\r
305 public File getFile() {
\r
310 * Returns the filename of this ICUFile object, without the path.
\r
312 * @return The filename of this ICUFile object, without the path.
\r
314 public String getFilename() {
\r
315 return icuFile.getName();
\r
319 * Returns the ICU version of this ICU4J jar.
\r
321 * @return The ICU version of this ICU4J jar.
\r
323 public String getICUVersion() {
\r
328 * Returns the path of this ICUFile object, without the filename.
\r
330 * @return The path of this ICUFile object, without the filename.
\r
332 public String getPath() {
\r
333 return icuFile.getAbsoluteFile().getParent();
\r
336 // public static String findURLTZVersion(File tzFile) {
\r
338 // File temp = File.createTempFile("zoneinfo", ".res");
\r
339 // temp.deleteOnExit();
\r
340 // copyFile(tzFile, temp);
\r
341 // return findTZVersion(temp);
\r
342 // } catch (IOException ex) {
\r
343 // ex.printStackTrace();
\r
349 * Returns the timezone resource version.
\r
351 * @return The timezone resource version.
\r
353 public String getTZVersion() {
\r
358 * Returns the result of getFile().toString().
\r
360 * @return The result of getFile().toString().
\r
362 public String toString() {
\r
363 return getFile().toString();
\r
367 * Updates the timezone resource in this ICUFile using <code>insertURL</code> as the source of
\r
368 * the new timezone resource and the backup directory <code>backupDir</code> to store a copy
\r
372 * The url location of the timezone resource to use.
\r
374 * The directory to store a backup for this ICUFile, or null if no backup.
\r
375 * @throws IOException
\r
376 * @throws InterruptedException
\r
378 public void update(URL insertURL, File backupDir) throws IOException, InterruptedException {
\r
379 String message = "Updating " + icuFile.getPath() + " ...";
\r
380 logger.printlnToBoth("");
\r
381 logger.printlnToBoth(message);
\r
383 if (!icuFile.canRead() || !icuFile.canWrite())
\r
384 throw new IOException("Missing permissions for " + icuFile.getPath());
\r
386 JarEntry[] jarEntries = new JarEntry[2];
\r
387 URL[] insertURLs = new URL[2];
\r
389 jarEntries[0] = tzEntry;
\r
390 insertURLs[0] = getCachedURL(insertURL);
\r
392 if (insertURLs[0] == null)
\r
393 throw new IOException(
\r
394 "Could not download the Time Zone data, skipping update for this jar.");
\r
396 // Check if metazoneInfo.res is available
\r
397 String tzURLStr = insertURL.toString();
\r
398 int lastSlashIdx = tzURLStr.lastIndexOf('/');
\r
399 if (lastSlashIdx >= 0) {
\r
400 String mzURLStr = tzURLStr.substring(0, lastSlashIdx + 1) + MZ_ENTRY_FILENAME;
\r
401 insertURLs[1] = getCachedURL(new URL(mzURLStr));
\r
402 if (insertURLs[1] != null) {
\r
403 jarEntries[1] = mzEntry;
\r
407 File backupFile = null;
\r
408 if ((backupFile = createBackupFile(icuFile, backupDir)) == null)
\r
409 throw new IOException(
\r
410 "Could not create an empty backup file (the original jar file remains unchanged).");
\r
411 if (!copyFile(icuFile, backupFile))
\r
412 throw new IOException(
\r
413 "Could not copy the original jar file to the backup location (the original jar file remains unchanged).");
\r
414 logger.printlnToBoth("Backup location: " + backupFile.getPath());
\r
415 if (!createUpdatedJar(backupFile, icuFile, jarEntries, insertURLs))
\r
416 throw new IOException(
\r
417 "Could not create an updated jar file at the original location (the original jar file is at the backup location).");
\r
419 // get the new timezone resource version
\r
420 tzVersion = findEntryTZVersion();
\r
422 message = "Successfully updated " + icuFile.getPath();
\r
423 logger.printlnToBoth(message);
\r
427 * Copies the jar entry <code>insertEntry</code> in <code>inputFile</code> to
\r
428 * <code>outputFile</code>.
\r
431 * The jar file containing <code>insertEntry</code>.
\r
432 * @param inputEntry
\r
433 * The entry to copy.
\r
434 * @param outputFile
\r
436 * @return Whether the operation was successful.
\r
438 private boolean copyEntry(File inputFile, JarEntry inputEntry, File outputFile) {
\r
439 logger.loglnToBoth("Copying from " + inputFile + "!/" + inputEntry + " to " + outputFile
\r
441 JarFile jar = null;
\r
442 InputStream istream = null;
\r
443 OutputStream ostream = null;
\r
444 byte[] buffer = new byte[BUFFER_SIZE];
\r
446 boolean success = false;
\r
449 jar = new JarFile(inputFile);
\r
450 istream = jar.getInputStream(inputEntry);
\r
451 ostream = new FileOutputStream(outputFile);
\r
453 while ((bytesRead = istream.read(buffer)) != -1)
\r
454 ostream.write(buffer, 0, bytesRead);
\r
457 logger.loglnToBoth("Copy successful.");
\r
458 } catch (IOException ex) {
\r
459 outputFile.delete();
\r
460 logger.loglnToBoth("Copy failed.");
\r
461 logger.logStackTraceToBoth(ex);
\r
463 // safely close the streams
\r
472 * Copies <code>inputFile</code> to <code>outputFile</code>.
\r
476 * @param outputFile
\r
478 * @return Whether the operation was successful.
\r
480 private boolean copyFile(File inputFile, File outputFile) {
\r
481 logger.loglnToBoth("Copying from " + inputFile + " to " + outputFile + ".");
\r
482 InputStream istream = null;
\r
483 OutputStream ostream = null;
\r
484 byte[] buffer = new byte[BUFFER_SIZE];
\r
486 boolean success = false;
\r
489 istream = new FileInputStream(inputFile);
\r
490 ostream = new FileOutputStream(outputFile);
\r
492 while ((bytesRead = istream.read(buffer)) != -1)
\r
493 ostream.write(buffer, 0, bytesRead);
\r
496 logger.loglnToBoth("Copy successful.");
\r
497 } catch (IOException ex) {
\r
498 outputFile.delete();
\r
499 logger.loglnToBoth("Copy failed.");
\r
500 logger.logStackTraceToBoth(ex);
\r
502 // safely close the streams
\r
510 * Creates a temporary file for the jar file <code>inputFile</code> under the directory
\r
511 * <code>backupBase</code> and returns it, or returns null if a temporary file could not be
\r
512 * created. Does not put any data in the newly created file yet.
\r
515 * The file to backup.
\r
516 * @param backupBase
\r
517 * The directory where backups are to be stored.
\r
518 * @return The temporary file that was created.
\r
520 private File createBackupFile(File inputFile, File backupBase) {
\r
521 logger.loglnToBoth("Creating backup file for " + inputFile + " at " + backupBase + ".");
\r
522 String filename = inputFile.getName();
\r
523 String suffix = ".jar";
\r
524 String prefix = filename.substring(0, filename.length() - suffix.length());
\r
526 if (backupBase == null) {
\r
528 // no backup directory means we need to create a temporary file
\r
529 // that will be deleted on exit
\r
530 File backupFile = File.createTempFile(prefix + "~", suffix);
\r
531 backupFile.deleteOnExit();
\r
533 } catch (IOException ex) {
\r
538 File backupFile = null;
\r
539 File backupDesc = null;
\r
540 File backupDir = new File(backupBase.getPath(), prefix);
\r
541 PrintStream ostream = null;
\r
544 backupBase.mkdir();
\r
546 backupFile = File.createTempFile(prefix + "~", suffix, backupDir);
\r
547 backupDesc = new File(backupDir.getPath(), backupFile.getName().substring(0,
\r
548 backupFile.getName().length() - suffix.length())
\r
550 backupDesc.createNewFile();
\r
551 ostream = new PrintStream(new FileOutputStream(backupDesc));
\r
552 ostream.println(inputFile.getPath());
\r
553 logger.loglnToBoth("Successfully created backup file at " + backupFile + ".");
\r
554 } catch (IOException ex) {
\r
555 logger.loglnToBoth("Failed to create backup file.");
\r
556 logger.logStackTraceToBoth(ex);
\r
557 if (backupFile != null)
\r
558 backupFile.delete();
\r
559 if (backupDesc != null)
\r
560 backupDesc.delete();
\r
561 backupDir.delete();
\r
571 * Copies <code>inputFile</code> to <code>outputFile</code>, replacing
\r
572 * <code>insertEntry</code> with <code>inputURL</code>.
\r
575 * The input jar file.
\r
576 * @param outputFile
\r
577 * The output jar file.
\r
578 * @param insertEntry
\r
579 * The entry to be replaced.
\r
581 * The URL to use in replacing the entry.
\r
582 * @return Whether the operation was successful.
\r
584 private boolean createUpdatedJar(File inputFile, File outputFile, JarEntry[] insertEntries,
\r
586 logger.loglnToBoth("Copying " + inputFile + " to " + outputFile + ",");
\r
587 for (int i = 0; i < insertEntries.length; i++) {
\r
588 if (insertEntries[i] != null) {
\r
589 logger.loglnToBoth(" replacing " + insertEntries[i] + " with " + inputURLs[i]);
\r
593 JarFile jar = null;
\r
594 JarOutputStream ostream = null;
\r
595 InputStream istream = null;
\r
596 InputStream jstream = null;
\r
597 byte[] buffer = new byte[BUFFER_SIZE];
\r
599 boolean success = false;
\r
600 Set possibleDuplicates = new HashSet();
\r
603 jar = new JarFile(inputFile);
\r
604 ostream = new JarOutputStream(new FileOutputStream(outputFile));
\r
606 Enumeration e = jar.entries();
\r
607 while (e.hasMoreElements()) {
\r
608 JarEntry currentEntry = (JarEntry) e.nextElement();
\r
609 String entryName = currentEntry.getName();
\r
610 if (entryName.startsWith(DUPLICATE_ENTRY_PATH)) {
\r
611 if (!possibleDuplicates.contains(entryName)) {
\r
612 possibleDuplicates.add(entryName);
\r
614 // ruh roh, we have a duplicate entry!
\r
615 // (just ignore it and continue)
\r
616 logger.printlnToBoth("Warning: Duplicate " + entryName
\r
617 + " found. Ignoring the duplicate.");
\r
622 boolean isReplaced = false;
\r
623 for (int i = 0; i < insertEntries.length; i++) {
\r
624 if (insertEntries[i] != null) {
\r
625 if (entryName.equals(insertEntries[i].getName())) {
\r
626 // if the current entry *is* the one that needs updating write a new entry based
\r
627 // on the input stream (from the URL)
\r
628 // currentEntry.setTime(System.currentTimeMillis());
\r
629 ostream.putNextEntry(new JarEntry(entryName));
\r
631 URLConnection con = inputURLs[i].openConnection();
\r
632 con.setRequestProperty("user-agent", System.getProperty("http.agent"));
\r
633 istream = con.getInputStream();
\r
635 while ((bytesRead = istream.read(buffer)) != -1) {
\r
636 ostream.write(buffer, 0, bytesRead);
\r
645 // if the current entry isn't the one that needs updating write a copy of the
\r
646 // old entry from the old file
\r
647 ostream.putNextEntry(new JarEntry(entryName));
\r
649 jstream = jar.getInputStream(currentEntry);
\r
650 while ((bytesRead = jstream.read(buffer)) != -1)
\r
651 ostream.write(buffer, 0, bytesRead);
\r
657 logger.loglnToBoth("Copy successful.");
\r
658 } catch (IOException ex) {
\r
659 outputFile.delete();
\r
660 logger.loglnToBoth("Copy failed:");
\r
661 logger.logStackTraceToBoth(ex);
\r
663 // safely close the streams
\r
673 * Performs the shared work of the constructors. Throws an IOException if <code>file</code>...
\r
675 * <li>does not exist</li>
\r
676 * <li>is not a file</li>
\r
677 * <li>ends with .ear or .war (these file types are unsupported)</li>
\r
678 * <li>does not end with .jar</li>
\r
679 * <li>is not updatable according the <code>isUpdatable</code></li>
\r
680 * <li>is signed.</li>
\r
682 * If an exception is not thrown, the ICUFile is fully initialized.
\r
685 * The file to wrap this ICUFile around.
\r
687 * The current logger.
\r
688 * @throws IOException
\r
690 private void initialize(File file, Logger log) throws IOException {
\r
691 this.icuFile = file;
\r
693 String message = null;
\r
695 if (!file.exists()) {
\r
696 message = "Skipped " + file.getPath() + " (does not exist).";
\r
697 } else if (!file.isFile()) {
\r
698 message = "Skipped " + file.getPath() + " (not a file).";
\r
699 } else if (file.getName().endsWith(".ear") || file.getName().endsWith(".war")) {
\r
700 message = "Skipped " + file.getPath()
\r
701 + " (this tool does not support .ear and .war files).";
\r
702 logger.loglnToBoth(message);
\r
703 } else if (!file.canRead() || !file.canWrite()) {
\r
704 message = "Skipped " + file.getPath() + " (missing permissions).";
\r
705 } else if (!file.getName().endsWith(".jar")) {
\r
706 message = "Skipped " + file.getPath() + " (not a jar file).";
\r
707 } else if (!isUpdatable()) {
\r
708 message = "Skipped " + file.getPath() + " (not an updatable ICU4J jar).";
\r
709 } else if (isSigned()) {
\r
710 message = "Skipped " + file.getPath() + " (cannot update signed jars).";
\r
711 logger.loglnToBoth(message);
\r
712 } else if (isEclipseFragment()) {
\r
713 message = "Skipped " + file.getPath()
\r
714 + " (eclipse fragments must be updated through ICU).";
\r
715 logger.loglnToBoth(message);
\r
718 if (message != null)
\r
719 throw new IOException(message);
\r
721 tzVersion = findEntryTZVersion();
\r
725 * Determines whether the current jar is an Eclipse Data Fragment.
\r
727 * @return Whether the current jar is an Eclipse Fragment.
\r
729 private boolean isEclipseDataFragment() {
\r
730 return (icuFile.getPath().indexOf("plugins" + File.separator + "com.ibm.icu.data.update") >= 0 && icuFile
\r
731 .getName().equalsIgnoreCase("icu-data.jar"));
\r
735 * Determines whether the current jar is an Eclipse Fragment.
\r
737 * @return Whether the current jar is an Eclipse Fragment.
\r
739 private boolean isEclipseFragment() {
\r
740 return (isEclipseDataFragment() || isEclipseMainFragment());
\r
744 * Determines whether the current jar is an Eclipse Main Fragment.
\r
746 * @return Whether the current jar is an Eclipse Fragment.
\r
748 private boolean isEclipseMainFragment() {
\r
749 return (icuFile.getPath().indexOf("plugins") >= 0 && icuFile.getName().startsWith(
\r
754 * Determines whether a timezone resource in a jar file is signed.
\r
756 * @return Whether a timezone resource in a jar file is signed.
\r
758 private boolean isSigned() {
\r
759 return tzEntry.getCertificates() != null;
\r
763 * Gathers information on the jar file represented by this ICUFile object and returns whether it
\r
764 * is an updatable ICU4J jar file.
\r
766 * @return Whether the jar file represented by this ICUFile object is an updatable ICU4J jar
\r
769 private boolean isUpdatable() {
\r
770 JarFile jar = null;
\r
771 boolean success = false;
\r
774 // open icuFile as a jar file
\r
775 jar = new JarFile(icuFile);
\r
777 // get its manifest to determine the ICU version
\r
778 Manifest manifest = jar.getManifest();
\r
779 icuVersion = ICU_VERSION_UNKNOWN;
\r
780 if (manifest != null) {
\r
781 Iterator iter = manifest.getEntries().values().iterator();
\r
782 while (iter.hasNext()) {
\r
783 Attributes attr = (Attributes) iter.next();
\r
784 String ver = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
\r
792 // if the jar's directory structure contains TZ_ENTRY_DIR and there
\r
793 // is a timezone resource in the jar, then the jar is updatable
\r
794 success = (jar.getJarEntry(TZ_ENTRY_DIR) != null)
\r
795 && ((this.tzEntry = getTZEntry(jar, TZ_ENTRY_FILENAME)) != null);
\r
797 // if the jar file contains metazoneInfo.res, initialize mzEntry -
\r
798 // this is true for ICU4J 3.8.1 or later releases
\r
800 mzEntry = getTZEntry(jar, MZ_ENTRY_FILENAME);
\r
802 } catch (IOException ex) {
\r
803 // unable to create the JarFile or unable to get the Manifest
\r
804 // log the unexplained i/o error, but we must drudge on
\r
805 logger.loglnToBoth("Error reading " + icuFile.getPath() + ".");
\r
806 logger.logStackTraceToBoth(ex);
\r
808 // close the jar gracefully
\r
809 if (!tryClose(jar))
\r
810 logger.errorln("Could not properly close the jar file " + icuFile + ".");
\r
813 // return whether the jar is updatable or not
\r
817 private URL getCachedURL(URL url) {
\r
818 File outputFile = (File) cacheMap.get(url);
\r
819 if (outputFile != null) {
\r
821 return outputFile.toURL();
\r
822 } catch (MalformedURLException ex) {
\r
826 InputStream istream = null;
\r
827 OutputStream ostream = null;
\r
828 byte[] buffer = new byte[BUFFER_SIZE];
\r
830 boolean success = false;
\r
833 String urlStr = url.toString();
\r
834 int lastSlash = urlStr.lastIndexOf('/');
\r
835 String fileName = lastSlash >= 0 ? urlStr.substring(lastSlash + 1) : urlStr;
\r
836 outputFile = File.createTempFile(fileName, null);
\r
837 outputFile.deleteOnExit();
\r
839 logger.loglnToBoth("Downloading from " + url + " to " + outputFile.getPath() + ".");
\r
841 URLConnection con = url.openConnection();
\r
842 con.setRequestProperty("user-agent", System.getProperty("http.agent"));
\r
843 istream = con.getInputStream();
\r
844 ostream = new FileOutputStream(outputFile);
\r
846 while ((bytesRead = istream.read(buffer)) != -1)
\r
847 ostream.write(buffer, 0, bytesRead);
\r
850 logger.loglnToBoth("Download successful.");
\r
851 } catch (IOException ex) {
\r
852 outputFile.delete();
\r
853 logger.loglnToBoth("Download failed.");
\r
854 logger.logStackTraceToBoth(ex);
\r
856 // safely close the streams
\r
861 return (success && outputFile != null) ? outputFile.toURL() : null;
\r
862 } catch (MalformedURLException ex) {
\r
869 * Tries to close <code>closeable</code> if possible.
\r
872 * A closeable object
\r
873 * @return false if an IOException occured, true otherwise.
\r
875 private boolean tryClose(InputStream closeable) {
\r
876 if (closeable != null)
\r
880 } catch (IOException ex) {
\r
888 * Tries to close <code>closeable</code> if possible.
\r
891 * A closeable object
\r
892 * @return false if an IOException occured, true otherwise.
\r
894 private boolean tryClose(OutputStream closeable) {
\r
895 if (closeable != null)
\r
899 } catch (IOException ex) {
\r
907 * Tries to close <code>closeable</code> if possible.
\r
910 * A closeable object
\r
911 * @return false if an IOException occured, true otherwise.
\r
913 private boolean tryClose(JarFile closeable) {
\r
914 if (closeable != null)
\r
918 } catch (IOException ex) {
\r