jdk/src/share/classes/java/time/zone/TzdbZoneRulesProvider.java
changeset 15658 55b829ca2334
parent 15289 3ac550392e43
child 16852 60207b2b4b42
equal deleted inserted replaced
15657:c588664d547e 15658:55b829ca2334
    64 import java.io.ByteArrayInputStream;
    64 import java.io.ByteArrayInputStream;
    65 import java.io.DataInputStream;
    65 import java.io.DataInputStream;
    66 import java.io.File;
    66 import java.io.File;
    67 import java.io.IOException;
    67 import java.io.IOException;
    68 import java.io.StreamCorruptedException;
    68 import java.io.StreamCorruptedException;
    69 import java.nio.file.FileSystems;
       
    70 import java.security.AccessController;
       
    71 import java.security.PrivilegedExceptionAction;
       
    72 import java.time.DateTimeException;
       
    73 import java.util.Arrays;
    69 import java.util.Arrays;
    74 import java.util.HashSet;
    70 import java.util.HashSet;
       
    71 import java.util.List;
       
    72 import java.util.Map;
    75 import java.util.NavigableMap;
    73 import java.util.NavigableMap;
    76 import java.util.Objects;
    74 import java.util.Objects;
    77 import java.util.Set;
    75 import java.util.Set;
    78 import java.util.TreeMap;
    76 import java.util.TreeMap;
    79 import java.util.concurrent.ConcurrentNavigableMap;
    77 import java.util.concurrent.ConcurrentHashMap;
    80 import java.util.concurrent.ConcurrentSkipListMap;
       
    81 import java.util.concurrent.CopyOnWriteArraySet;
       
    82 import java.util.concurrent.atomic.AtomicReferenceArray;
       
    83 import java.util.zip.ZipFile;
    78 import java.util.zip.ZipFile;
    84 
    79 
    85 /**
    80 /**
    86  * Loads time-zone rules for 'TZDB'.
    81  * Loads time-zone rules for 'TZDB'.
    87  * <p>
       
    88  * This class is public for the service loader to access.
       
    89  *
       
    90  * <h3>Specification for implementors</h3>
       
    91  * This class is immutable and thread-safe.
       
    92  *
    82  *
    93  * @since 1.8
    83  * @since 1.8
    94  */
    84  */
    95 final class TzdbZoneRulesProvider extends ZoneRulesProvider {
    85 final class TzdbZoneRulesProvider extends ZoneRulesProvider {
    96     // service loader seems to need it to be public
       
    97 
    86 
    98     /**
    87     /**
    99      * All the regions that are available.
    88      * All the regions that are available.
   100      */
    89      */
   101     private final Set<String> regionIds = new CopyOnWriteArraySet<>();
    90     private List<String> regionIds;
   102     /**
    91     /**
   103      * All the versions that are available.
    92      * Version Id of this tzdb rules
   104      */
    93      */
   105     private final ConcurrentNavigableMap<String, Version> versions = new ConcurrentSkipListMap<>();
    94     private String versionId;
       
    95     /**
       
    96      * Region to rules mapping
       
    97      */
       
    98     private final Map<String, Object> regionToRules = new ConcurrentHashMap<>();
   106 
    99 
   107     /**
   100     /**
   108      * Creates an instance.
   101      * Creates an instance.
   109      * Created by the {@code ServiceLoader}.
   102      * Created by the {@code ServiceLoader}.
   110      *
   103      *
   111      * @throws ZoneRulesException if unable to load
   104      * @throws ZoneRulesException if unable to load
   112      */
   105      */
   113     public TzdbZoneRulesProvider() {
   106     public TzdbZoneRulesProvider() {
   114         super();
   107         try {
   115         if (load(ClassLoader.getSystemClassLoader()) == false) {
   108             String libDir = System.getProperty("java.home") + File.separator + "lib";
   116             throw new ZoneRulesException("No time-zone rules found for 'TZDB'");
   109             File tzdbJar = new File(libDir, "tzdb.jar");
   117         }
   110             try (ZipFile zf = new ZipFile(tzdbJar);
   118     }
   111                  DataInputStream dis = new DataInputStream(
   119 
   112                      zf.getInputStream(zf.getEntry("TZDB.dat")))) {
   120     //-----------------------------------------------------------------------
   113                 load(dis);
       
   114             }
       
   115         } catch (Exception ex) {
       
   116             throw new ZoneRulesException("Unable to load TZDB time-zone rules", ex);
       
   117         }
       
   118     }
       
   119 
   121     @Override
   120     @Override
   122     protected Set<String> provideZoneIds() {
   121     protected Set<String> provideZoneIds() {
   123         return new HashSet<>(regionIds);
   122         return new HashSet<>(regionIds);
   124     }
   123     }
   125 
   124 
   126     @Override
   125     @Override
   127     protected ZoneRules provideRules(String zoneId) {
   126     protected ZoneRules provideRules(String zoneId, boolean forCaching) {
   128         Objects.requireNonNull(zoneId, "zoneId");
   127         // forCaching flag is ignored because this is not a dynamic provider
   129         ZoneRules rules = versions.lastEntry().getValue().getRules(zoneId);
   128         Object obj = regionToRules.get(zoneId);
   130         if (rules == null) {
   129         if (obj == null) {
   131             throw new ZoneRulesException("Unknown time-zone ID: " + zoneId);
   130             throw new ZoneRulesException("Unknown time-zone ID: " + zoneId);
   132         }
   131         }
   133         return rules;
   132         try {
       
   133             if (obj instanceof byte[]) {
       
   134                 byte[] bytes = (byte[]) obj;
       
   135                 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
       
   136                 obj = Ser.read(dis);
       
   137                 regionToRules.put(zoneId, obj);
       
   138             }
       
   139             return (ZoneRules) obj;
       
   140         } catch (Exception ex) {
       
   141             throw new ZoneRulesException("Invalid binary time-zone data: TZDB:" + zoneId + ", version: " + versionId, ex);
       
   142         }
   134     }
   143     }
   135 
   144 
   136     @Override
   145     @Override
   137     protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
   146     protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
   138         TreeMap<String, ZoneRules> map = new TreeMap<>();
   147         TreeMap<String, ZoneRules> map = new TreeMap<>();
   139         for (Version version : versions.values()) {
   148         ZoneRules rules = getRules(zoneId, false);
   140             ZoneRules rules = version.getRules(zoneId);
   149         if (rules != null) {
   141             if (rules != null) {
   150             map.put(versionId, rules);
   142                 map.put(version.versionId, rules);
       
   143             }
       
   144         }
   151         }
   145         return map;
   152         return map;
   146     }
       
   147 
       
   148     //-------------------------------------------------------------------------
       
   149     /**
       
   150      * Loads the rules.
       
   151      *
       
   152      * @param classLoader  the class loader to use, not null
       
   153      * @return true if updated
       
   154      * @throws ZoneRulesException if unable to load
       
   155      */
       
   156     private boolean load(ClassLoader classLoader) {
       
   157         Object updated = Boolean.FALSE;
       
   158         try {
       
   159             updated = AccessController.doPrivileged(new PrivilegedExceptionAction() {
       
   160                 public Object run() throws IOException, ClassNotFoundException {
       
   161                     File tzdbJar = null;
       
   162                     // TBD: workaround for now, so test/java/time tests can be
       
   163                     //      run against Java runtime that does not have tzdb
       
   164                     String tzdbProp = System.getProperty("java.time.zone.tzdbjar");
       
   165                     if (tzdbProp != null) {
       
   166                         tzdbJar = new File(tzdbProp);
       
   167                     } else {
       
   168                         String libDir = System.getProperty("java.home") + File.separator + "lib";
       
   169                         try {
       
   170                             libDir = FileSystems.getDefault().getPath(libDir).toRealPath().toString();
       
   171                         } catch(Exception e) {}
       
   172                         tzdbJar = new File(libDir, "tzdb.jar");
       
   173                     }
       
   174                     try (ZipFile zf = new ZipFile(tzdbJar);
       
   175                          DataInputStream dis = new DataInputStream(
       
   176                              zf.getInputStream(zf.getEntry("TZDB.dat")))) {
       
   177                         Iterable<Version> loadedVersions = load(dis);
       
   178                         for (Version loadedVersion : loadedVersions) {
       
   179                             if (versions.putIfAbsent(loadedVersion.versionId, loadedVersion) != null) {
       
   180                                 throw new DateTimeException(
       
   181                                     "Data already loaded for TZDB time-zone rules version: " +
       
   182                                     loadedVersion.versionId);
       
   183                             }
       
   184                         }
       
   185                     }
       
   186                     return Boolean.TRUE;
       
   187                 }
       
   188             });
       
   189         } catch (Exception ex) {
       
   190             throw new ZoneRulesException("Unable to load TZDB time-zone rules", ex);
       
   191         }
       
   192         return updated == Boolean.TRUE;
       
   193     }
   153     }
   194 
   154 
   195     /**
   155     /**
   196      * Loads the rules from a DateInputStream, often in a jar file.
   156      * Loads the rules from a DateInputStream, often in a jar file.
   197      *
   157      *
   198      * @param dis  the DateInputStream to load, not null
   158      * @param dis  the DateInputStream to load, not null
   199      * @throws Exception if an error occurs
   159      * @throws Exception if an error occurs
   200      */
   160      */
   201     private Iterable<Version> load(DataInputStream dis) throws ClassNotFoundException, IOException {
   161     private void load(DataInputStream dis) throws Exception {
   202         if (dis.readByte() != 1) {
   162         if (dis.readByte() != 1) {
   203             throw new StreamCorruptedException("File format not recognised");
   163             throw new StreamCorruptedException("File format not recognised");
   204         }
   164         }
   205         // group
   165         // group
   206         String groupId = dis.readUTF();
   166         String groupId = dis.readUTF();
   207         if ("TZDB".equals(groupId) == false) {
   167         if ("TZDB".equals(groupId) == false) {
   208             throw new StreamCorruptedException("File format not recognised");
   168             throw new StreamCorruptedException("File format not recognised");
   209         }
   169         }
   210         // versions
   170         // versions
   211         int versionCount = dis.readShort();
   171         int versionCount = dis.readShort();
   212         String[] versionArray = new String[versionCount];
       
   213         for (int i = 0; i < versionCount; i++) {
   172         for (int i = 0; i < versionCount; i++) {
   214             versionArray[i] = dis.readUTF();
   173             versionId = dis.readUTF();
   215         }
   174         }
   216         // regions
   175         // regions
   217         int regionCount = dis.readShort();
   176         int regionCount = dis.readShort();
   218         String[] regionArray = new String[regionCount];
   177         String[] regionArray = new String[regionCount];
   219         for (int i = 0; i < regionCount; i++) {
   178         for (int i = 0; i < regionCount; i++) {
   220             regionArray[i] = dis.readUTF();
   179             regionArray[i] = dis.readUTF();
   221         }
   180         }
   222         regionIds.addAll(Arrays.asList(regionArray));
   181         regionIds = Arrays.asList(regionArray);
   223         // rules
   182         // rules
   224         int ruleCount = dis.readShort();
   183         int ruleCount = dis.readShort();
   225         Object[] ruleArray = new Object[ruleCount];
   184         Object[] ruleArray = new Object[ruleCount];
   226         for (int i = 0; i < ruleCount; i++) {
   185         for (int i = 0; i < ruleCount; i++) {
   227             byte[] bytes = new byte[dis.readShort()];
   186             byte[] bytes = new byte[dis.readShort()];
   228             dis.readFully(bytes);
   187             dis.readFully(bytes);
   229             ruleArray[i] = bytes;
   188             ruleArray[i] = bytes;
   230         }
   189         }
   231         AtomicReferenceArray<Object> ruleData = new AtomicReferenceArray<>(ruleArray);
       
   232         // link version-region-rules
   190         // link version-region-rules
   233         Set<Version> versionSet = new HashSet<Version>(versionCount);
       
   234         for (int i = 0; i < versionCount; i++) {
   191         for (int i = 0; i < versionCount; i++) {
   235             int versionRegionCount = dis.readShort();
   192             int versionRegionCount = dis.readShort();
   236             String[] versionRegionArray = new String[versionRegionCount];
   193             regionToRules.clear();
   237             short[] versionRulesArray = new short[versionRegionCount];
       
   238             for (int j = 0; j < versionRegionCount; j++) {
   194             for (int j = 0; j < versionRegionCount; j++) {
   239                 versionRegionArray[j] = regionArray[dis.readShort()];
   195                 String region = regionArray[dis.readShort()];
   240                 versionRulesArray[j] = dis.readShort();
   196                 Object rule = ruleArray[dis.readShort() & 0xffff];
       
   197                 regionToRules.put(region, rule);
   241             }
   198             }
   242             versionSet.add(new Version(versionArray[i], versionRegionArray, versionRulesArray, ruleData));
   199         }
   243         }
       
   244         return versionSet;
       
   245     }
   200     }
   246 
   201 
   247     @Override
   202     @Override
   248     public String toString() {
   203     public String toString() {
   249         return "TZDB";
   204         return "TZDB[" + versionId + "]";
   250     }
   205     }
   251 
       
   252     //-----------------------------------------------------------------------
       
   253     /**
       
   254      * A version of the TZDB rules.
       
   255      */
       
   256     static class Version {
       
   257         private final String versionId;
       
   258         private final String[] regionArray;
       
   259         private final short[] ruleIndices;
       
   260         private final AtomicReferenceArray<Object> ruleData;
       
   261 
       
   262         Version(String versionId, String[] regionIds, short[] ruleIndices, AtomicReferenceArray<Object> ruleData) {
       
   263             this.ruleData = ruleData;
       
   264             this.versionId = versionId;
       
   265             this.regionArray = regionIds;
       
   266             this.ruleIndices = ruleIndices;
       
   267         }
       
   268 
       
   269         ZoneRules getRules(String regionId) {
       
   270             int regionIndex = Arrays.binarySearch(regionArray, regionId);
       
   271             if (regionIndex < 0) {
       
   272                 return null;
       
   273             }
       
   274             try {
       
   275                 return createRule(ruleIndices[regionIndex]);
       
   276             } catch (Exception ex) {
       
   277                 throw new ZoneRulesException("Invalid binary time-zone data: TZDB:" + regionId + ", version: " + versionId, ex);
       
   278             }
       
   279         }
       
   280 
       
   281         ZoneRules createRule(short index) throws Exception {
       
   282             Object obj = ruleData.get(index);
       
   283             if (obj instanceof byte[]) {
       
   284                 byte[] bytes = (byte[]) obj;
       
   285                 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
       
   286                 obj = Ser.read(dis);
       
   287                 ruleData.set(index, obj);
       
   288             }
       
   289             return (ZoneRules) obj;
       
   290         }
       
   291 
       
   292         @Override
       
   293         public String toString() {
       
   294             return versionId;
       
   295         }
       
   296     }
       
   297 
       
   298 }
   206 }