]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/dev/tool/tzu/ICUFile.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / dev / tool / tzu / ICUFile.java
1 /*\r
2  * ******************************************************************************\r
3  * Copyright (C) 2007-2008, International Business Machines Corporation and others.\r
4  * All Rights Reserved.\r
5  * ******************************************************************************\r
6  */\r
7 package com.ibm.icu.dev.tool.tzu;\r
8 \r
9 import java.io.File;\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
34 \r
35 /**\r
36  * A class that represents an updatable ICU4J jar file. A file is an updatable ICU4J jar file if it\r
37  * <ul>\r
38  * <li>exists</li>\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
44  * </ul>\r
45  */\r
46 public class ICUFile {\r
47     /**\r
48      * ICU version to use if one cannot be found.\r
49      */\r
50     public static final String ICU_VERSION_UNKNOWN = "Unknown";\r
51 \r
52     /**\r
53      * A directory entry that is found in every updatable ICU4J jar file.\r
54      */\r
55     public static final String TZ_ENTRY_DIR = "com/ibm/icu/impl";\r
56 \r
57     /**\r
58      * The timezone resource filename.\r
59      */\r
60     public static final String TZ_ENTRY_FILENAME = "zoneinfo.res";\r
61 \r
62     /**\r
63      * The metazone resource filename.\r
64      */\r
65     private static final String MZ_ENTRY_FILENAME = "metazoneInfo.res";\r
66 \r
67     /**\r
68      * Key to use when getting the version of a timezone resource.\r
69      */\r
70     public static final String TZ_VERSION_KEY = "TZVersion";\r
71 \r
72     /**\r
73      * Timezone version to use if one cannot be found.\r
74      */\r
75     public static final String TZ_VERSION_UNKNOWN = "Unknown";\r
76 \r
77     /**\r
78      * Jar entry path where some files are duplicated. This is an issue with icu4j 3.8.0.\r
79      */\r
80     public static final String DUPLICATE_ENTRY_PATH = "com/ibm/icu/impl/duration/impl/data";\r
81 \r
82     /**\r
83      * The buffer size to use for copying data.\r
84      */\r
85     private static final int BUFFER_SIZE = 1024;\r
86 \r
87     /**\r
88      * A map that caches links from URLs to time zone data to their downloaded File counterparts.\r
89      */\r
90     private static final Map cacheMap = new HashMap();\r
91 \r
92     /**\r
93      * Determines the version of a timezone resource as a standard file without locking the file.\r
94      * \r
95      * @param tzFile\r
96      *            The file representing the timezone resource.\r
97      * @param logger\r
98      *            The current logger.\r
99      * @return The version of the timezone resource.\r
100      */\r
101     public static String findFileTZVersion(File tzFile, Logger logger) {\r
102         ICUFile rawTZFile = new ICUFile(logger);\r
103 \r
104         try {\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
111             return null;\r
112         }\r
113     }\r
114 \r
115     /**\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
118      * \r
119      * @param tzFile\r
120      *            The file representing the timezone resource.\r
121      * @param logger\r
122      *            The current logger.\r
123      * @return The version of the timezone resource.\r
124      */\r
125     private static String findTZVersion(File tzFile, Logger logger) {\r
126         try {\r
127             String filename = tzFile.getName();\r
128             String entryname = filename.substring(0, filename.length() - ".res".length());\r
129 \r
130             URL url = new URL(tzFile.getAbsoluteFile().getParentFile().toURL().toString());\r
131             ClassLoader loader = new URLClassLoader(new URL[] { url });\r
132 \r
133             // UResourceBundle bundle = UResourceBundle.getBundleInstance("",\r
134             // entryname, loader);\r
135 \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
142 \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
149                     return tzVersion;\r
150             }\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
174             }\r
175         }\r
176 \r
177         return TZ_VERSION_UNKNOWN;\r
178     }\r
179 \r
180     /**\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
183      * \r
184      * @param jar       The jar file to search.\r
185      * @param entryName The target entry name\r
186      *              \r
187      * @return The jar entry representing the timezone resource in the jar file, or null if none is\r
188      *         found.\r
189      */\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
196                 return tzEntry;\r
197         }\r
198         return null;\r
199     }\r
200 \r
201     /**\r
202      * The ICU4J jar file represented by this ICUFile.\r
203      */\r
204     private File icuFile;\r
205 \r
206     /**\r
207      * The ICU version of the ICU4J jar.\r
208      */\r
209     private String icuVersion;\r
210 \r
211     /**\r
212      * The current logger.\r
213      */\r
214     private Logger logger;\r
215 \r
216     /**\r
217      * The entry for the timezone resource inside the ICU4J jar.\r
218      */\r
219     private JarEntry tzEntry;\r
220 \r
221     /**\r
222      * The entry for the metazone resource inside the ICU4J jar.\r
223      */\r
224     private JarEntry mzEntry;\r
225 \r
226     /**\r
227      * The version of the timezone resource inside the ICU4J jar.\r
228      */\r
229     private String tzVersion;\r
230 \r
231     /**\r
232      * Constructs an ICUFile around a file. See <code>initialize</code> for details.\r
233      * \r
234      * @param file\r
235      *            The file to wrap this ICUFile around.\r
236      * @param logger\r
237      *            The current logger.\r
238      * @throws IOException\r
239      */\r
240     public ICUFile(File file, Logger logger) throws IOException {\r
241         initialize(file, logger);\r
242     }\r
243 \r
244     /**\r
245      * Constructs an ICUFile around a file. See <code>initialize</code> for details.\r
246      * \r
247      * @param filename\r
248      *            The file to wrap this ICUFile around.\r
249      * @param logger\r
250      *            The current logger.\r
251      * @throws IOException\r
252      */\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
256 \r
257         initialize(new File(filename), logger);\r
258     }\r
259 \r
260     /**\r
261      * Constructs a blank ICUFile. Used internally for timezone resource files that are not\r
262      * contained within a jar.\r
263      * \r
264      * @param logger\r
265      *            The current logger.\r
266      */\r
267     private ICUFile(Logger logger) {\r
268         this.logger = logger;\r
269     }\r
270 \r
271     /**\r
272      * Compares two ICUFiles by the file they represent.\r
273      * \r
274      * @param other\r
275      *            The other ICUFile to compare to.\r
276      * @return Whether the files represented by the two ICUFiles are equal.\r
277      */\r
278     public boolean equals(Object other) {\r
279         return (!(other instanceof ICUFile)) ? false : icuFile.getAbsoluteFile().equals(\r
280                 ((ICUFile) other).icuFile.getAbsoluteFile());\r
281     }\r
282 \r
283     /**\r
284      * Determines the version of a timezone resource in a jar file without locking the jar file.\r
285      * \r
286      * @return The version of the timezone resource.\r
287      */\r
288     public String findEntryTZVersion() {\r
289         try {\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
296             return null;\r
297         }\r
298     }\r
299 \r
300     /**\r
301      * Returns the File object represented by this ICUFile object.\r
302      * \r
303      * @return The File object represented by this ICUFile object.\r
304      */\r
305     public File getFile() {\r
306         return icuFile;\r
307     }\r
308 \r
309     /**\r
310      * Returns the filename of this ICUFile object, without the path.\r
311      * \r
312      * @return The filename of this ICUFile object, without the path.\r
313      */\r
314     public String getFilename() {\r
315         return icuFile.getName();\r
316     }\r
317 \r
318     /**\r
319      * Returns the ICU version of this ICU4J jar.\r
320      * \r
321      * @return The ICU version of this ICU4J jar.\r
322      */\r
323     public String getICUVersion() {\r
324         return icuVersion;\r
325     }\r
326 \r
327     /**\r
328      * Returns the path of this ICUFile object, without the filename.\r
329      * \r
330      * @return The path of this ICUFile object, without the filename.\r
331      */\r
332     public String getPath() {\r
333         return icuFile.getAbsoluteFile().getParent();\r
334     }\r
335 \r
336     // public static String findURLTZVersion(File tzFile) {\r
337     // try {\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
344     // return null;\r
345     // }\r
346     // }\r
347 \r
348     /**\r
349      * Returns the timezone resource version.\r
350      * \r
351      * @return The timezone resource version.\r
352      */\r
353     public String getTZVersion() {\r
354         return tzVersion;\r
355     }\r
356 \r
357     /**\r
358      * Returns the result of getFile().toString().\r
359      * \r
360      * @return The result of getFile().toString().\r
361      */\r
362     public String toString() {\r
363         return getFile().toString();\r
364     }\r
365 \r
366     /**\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
369      * of the ICUFile.\r
370      * \r
371      * @param insertURL\r
372      *            The url location of the timezone resource to use.\r
373      * @param backupDir\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
377      */\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
382 \r
383         if (!icuFile.canRead() || !icuFile.canWrite())\r
384             throw new IOException("Missing permissions for " + icuFile.getPath());\r
385 \r
386         JarEntry[] jarEntries = new JarEntry[2];\r
387         URL[] insertURLs = new URL[2];\r
388 \r
389         jarEntries[0] = tzEntry;\r
390         insertURLs[0] = getCachedURL(insertURL);\r
391 \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
395 \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
404             }\r
405         }\r
406 \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
418 \r
419         // get the new timezone resource version\r
420         tzVersion = findEntryTZVersion();\r
421 \r
422         message = "Successfully updated " + icuFile.getPath();\r
423         logger.printlnToBoth(message);\r
424     }\r
425 \r
426     /**\r
427      * Copies the jar entry <code>insertEntry</code> in <code>inputFile</code> to\r
428      * <code>outputFile</code>.\r
429      * \r
430      * @param inputFile\r
431      *            The jar file containing <code>insertEntry</code>.\r
432      * @param inputEntry\r
433      *            The entry to copy.\r
434      * @param outputFile\r
435      *            The output file.\r
436      * @return Whether the operation was successful.\r
437      */\r
438     private boolean copyEntry(File inputFile, JarEntry inputEntry, File outputFile) {\r
439         logger.loglnToBoth("Copying from " + inputFile + "!/" + inputEntry + " to " + outputFile\r
440                 + ".");\r
441         JarFile jar = null;\r
442         InputStream istream = null;\r
443         OutputStream ostream = null;\r
444         byte[] buffer = new byte[BUFFER_SIZE];\r
445         int bytesRead;\r
446         boolean success = false;\r
447 \r
448         try {\r
449             jar = new JarFile(inputFile);\r
450             istream = jar.getInputStream(inputEntry);\r
451             ostream = new FileOutputStream(outputFile);\r
452 \r
453             while ((bytesRead = istream.read(buffer)) != -1)\r
454                 ostream.write(buffer, 0, bytesRead);\r
455 \r
456             success = true;\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
462         } finally {\r
463             // safely close the streams\r
464             tryClose(jar);\r
465             tryClose(istream);\r
466             tryClose(ostream);\r
467         }\r
468         return success;\r
469     }\r
470 \r
471     /**\r
472      * Copies <code>inputFile</code> to <code>outputFile</code>.\r
473      * \r
474      * @param inputFile\r
475      *            The input file.\r
476      * @param outputFile\r
477      *            The output file.\r
478      * @return Whether the operation was successful.\r
479      */\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
485         int bytesRead;\r
486         boolean success = false;\r
487 \r
488         try {\r
489             istream = new FileInputStream(inputFile);\r
490             ostream = new FileOutputStream(outputFile);\r
491 \r
492             while ((bytesRead = istream.read(buffer)) != -1)\r
493                 ostream.write(buffer, 0, bytesRead);\r
494 \r
495             success = true;\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
501         } finally {\r
502             // safely close the streams\r
503             tryClose(istream);\r
504             tryClose(ostream);\r
505         }\r
506         return success;\r
507     }\r
508 \r
509     /**\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
513      * \r
514      * @param inputFile\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
519      */\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
525 \r
526         if (backupBase == null) {\r
527             try {\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
532                 return backupFile;\r
533             } catch (IOException ex) {\r
534                 return null;\r
535             }\r
536         }\r
537 \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
542 \r
543         try {\r
544             backupBase.mkdir();\r
545             backupDir.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
549                     + ".txt");\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
562             backupFile = null;\r
563         } finally {\r
564             tryClose(ostream);\r
565         }\r
566 \r
567         return backupFile;\r
568     }\r
569 \r
570     /**\r
571      * Copies <code>inputFile</code> to <code>outputFile</code>, replacing\r
572      * <code>insertEntry</code> with <code>inputURL</code>.\r
573      * \r
574      * @param inputFile\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
580      * @param inputURL\r
581      *            The URL to use in replacing the entry.\r
582      * @return Whether the operation was successful.\r
583      */\r
584     private boolean createUpdatedJar(File inputFile, File outputFile, JarEntry[] insertEntries,\r
585             URL[] inputURLs) {\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
590             }\r
591         }\r
592 \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
598         int bytesRead;\r
599         boolean success = false;\r
600         Set possibleDuplicates = new HashSet();\r
601 \r
602         try {\r
603             jar = new JarFile(inputFile);\r
604             ostream = new JarOutputStream(new FileOutputStream(outputFile));\r
605 \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
613                     } else {\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
618                         continue;\r
619                     }\r
620                 }\r
621 \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
630 \r
631                             URLConnection con = inputURLs[i].openConnection();\r
632                             con.setRequestProperty("user-agent", System.getProperty("http.agent"));\r
633                             istream = con.getInputStream();\r
634 \r
635                             while ((bytesRead = istream.read(buffer)) != -1) {\r
636                                 ostream.write(buffer, 0, bytesRead);\r
637                             }\r
638                             istream.close();\r
639                             isReplaced = true;\r
640                             break;\r
641                         }\r
642                     }\r
643                 }\r
644                 if (!isReplaced) {\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
648 \r
649                     jstream = jar.getInputStream(currentEntry);\r
650                     while ((bytesRead = jstream.read(buffer)) != -1)\r
651                         ostream.write(buffer, 0, bytesRead);\r
652                     jstream.close();\r
653                 }\r
654             }\r
655 \r
656             success = true;\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
662         } finally {\r
663             // safely close the streams\r
664             tryClose(istream);\r
665             tryClose(ostream);\r
666             tryClose(jstream);\r
667             tryClose(jar);\r
668         }\r
669         return success;\r
670     }\r
671 \r
672     /**\r
673      * Performs the shared work of the constructors. Throws an IOException if <code>file</code>...\r
674      * <ul>\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
681      * </ul>\r
682      * If an exception is not thrown, the ICUFile is fully initialized.\r
683      * \r
684      * @param file\r
685      *            The file to wrap this ICUFile around.\r
686      * @param logger\r
687      *            The current logger.\r
688      * @throws IOException\r
689      */\r
690     private void initialize(File file, Logger log) throws IOException {\r
691         this.icuFile = file;\r
692         this.logger = log;\r
693         String message = null;\r
694 \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
716         }\r
717 \r
718         if (message != null)\r
719             throw new IOException(message);\r
720 \r
721         tzVersion = findEntryTZVersion();\r
722     }\r
723 \r
724     /**\r
725      * Determines whether the current jar is an Eclipse Data Fragment.\r
726      * \r
727      * @return Whether the current jar is an Eclipse Fragment.\r
728      */\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
732     }\r
733 \r
734     /**\r
735      * Determines whether the current jar is an Eclipse Fragment.\r
736      * \r
737      * @return Whether the current jar is an Eclipse Fragment.\r
738      */\r
739     private boolean isEclipseFragment() {\r
740         return (isEclipseDataFragment() || isEclipseMainFragment());\r
741     }\r
742 \r
743     /**\r
744      * Determines whether the current jar is an Eclipse Main Fragment.\r
745      * \r
746      * @return Whether the current jar is an Eclipse Fragment.\r
747      */\r
748     private boolean isEclipseMainFragment() {\r
749         return (icuFile.getPath().indexOf("plugins") >= 0 && icuFile.getName().startsWith(\r
750                 "com.ibm.icu_"));\r
751     }\r
752 \r
753     /**\r
754      * Determines whether a timezone resource in a jar file is signed.\r
755      * \r
756      * @return Whether a timezone resource in a jar file is signed.\r
757      */\r
758     private boolean isSigned() {\r
759         return tzEntry.getCertificates() != null;\r
760     }\r
761 \r
762     /**\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
765      * \r
766      * @return Whether the jar file represented by this ICUFile object is an updatable ICU4J jar\r
767      *         file.\r
768      */\r
769     private boolean isUpdatable() {\r
770         JarFile jar = null;\r
771         boolean success = false;\r
772 \r
773         try {\r
774             // open icuFile as a jar file\r
775             jar = new JarFile(icuFile);\r
776 \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
785                     if (ver != null) {\r
786                         icuVersion = ver;\r
787                         break;\r
788                     }\r
789                 }\r
790             }\r
791 \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
796 \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
799             if (success) {\r
800                 mzEntry = getTZEntry(jar, MZ_ENTRY_FILENAME);\r
801             }\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
807         } finally {\r
808             // close the jar gracefully\r
809             if (!tryClose(jar))\r
810                 logger.errorln("Could not properly close the jar file " + icuFile + ".");\r
811         }\r
812 \r
813         // return whether the jar is updatable or not\r
814         return success;\r
815     }\r
816 \r
817     private URL getCachedURL(URL url) {\r
818         File outputFile = (File) cacheMap.get(url);\r
819         if (outputFile != null) {\r
820             try {\r
821                 return outputFile.toURL();\r
822             } catch (MalformedURLException ex) {\r
823                 return null;\r
824             }\r
825         } else {\r
826             InputStream istream = null;\r
827             OutputStream ostream = null;\r
828             byte[] buffer = new byte[BUFFER_SIZE];\r
829             int bytesRead;\r
830             boolean success = false;\r
831 \r
832             try {\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
838 \r
839                 logger.loglnToBoth("Downloading from " + url + " to " + outputFile.getPath() + ".");\r
840 \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
845 \r
846                 while ((bytesRead = istream.read(buffer)) != -1)\r
847                     ostream.write(buffer, 0, bytesRead);\r
848 \r
849                 success = true;\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
855             } finally {\r
856                 // safely close the streams\r
857                 tryClose(istream);\r
858                 tryClose(ostream);\r
859             }\r
860             try {\r
861                 return (success && outputFile != null) ? outputFile.toURL() : null;\r
862             } catch (MalformedURLException ex) {\r
863                 return null;\r
864             }\r
865         }\r
866     }\r
867 \r
868     /**\r
869      * Tries to close <code>closeable</code> if possible.\r
870      * \r
871      * @param closeable\r
872      *            A closeable object\r
873      * @return false if an IOException occured, true otherwise.\r
874      */\r
875     private boolean tryClose(InputStream closeable) {\r
876         if (closeable != null)\r
877             try {\r
878                 closeable.close();\r
879                 return true;\r
880             } catch (IOException ex) {\r
881                 return false;\r
882             }\r
883         else\r
884             return true;\r
885     }\r
886 \r
887     /**\r
888      * Tries to close <code>closeable</code> if possible.\r
889      * \r
890      * @param closeable\r
891      *            A closeable object\r
892      * @return false if an IOException occured, true otherwise.\r
893      */\r
894     private boolean tryClose(OutputStream closeable) {\r
895         if (closeable != null)\r
896             try {\r
897                 closeable.close();\r
898                 return true;\r
899             } catch (IOException ex) {\r
900                 return false;\r
901             }\r
902         else\r
903             return true;\r
904     }\r
905 \r
906     /**\r
907      * Tries to close <code>closeable</code> if possible.\r
908      * \r
909      * @param closeable\r
910      *            A closeable object\r
911      * @return false if an IOException occured, true otherwise.\r
912      */\r
913     private boolean tryClose(JarFile closeable) {\r
914         if (closeable != null)\r
915             try {\r
916                 closeable.close();\r
917                 return true;\r
918             } catch (IOException ex) {\r
919                 return false;\r
920             }\r
921         else\r
922             return true;\r
923     }\r
924 }\r