1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * This file is available under and governed by the GNU General Public 28 * License version 2 only, as published by the Free Software Foundation. 29 * However, the following notice accompanied the original version of this 30 * file: 31 * 32 * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos 33 * 34 * All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions are met: 38 * 39 * * Redistributions of source code must retain the above copyright notice, 40 * this list of conditions and the following disclaimer. 41 * 42 * * Redistributions in binary form must reproduce the above copyright notice, 43 * this list of conditions and the following disclaimer in the documentation 44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package java.time.zone; 63 64 import java.time.DateTimeException; 65 import java.time.ZoneId; 66 import java.time.ZonedDateTime; 67 import java.util.ArrayList; 68 import java.util.Collections; 69 import java.util.HashSet; 70 import java.util.Iterator; 71 import java.util.List; 72 import java.util.NavigableMap; 73 import java.util.Objects; 74 import java.util.ServiceConfigurationError; 75 import java.util.ServiceLoader; 76 import java.util.Set; 77 import java.util.concurrent.ConcurrentHashMap; 78 import java.util.concurrent.ConcurrentMap; 79 import java.util.concurrent.CopyOnWriteArrayList; 80 81 /** 82 * Provider of time-zone rules to the system. 83 * <p> 84 * This class manages the configuration of time-zone rules. 85 * The static methods provide the public API that can be used to manage the providers. 86 * The abstract methods provide the SPI that allows rules to be provided. 87 * <p> 88 * Rules are looked up primarily by zone ID, as used by {@link ZoneId}. 89 * Only zone region IDs may be used, zone offset IDs are not used here. 90 * <p> 91 * Time-zone rules are political, thus the data can change at any time. 92 * Each provider will provide the latest rules for each zone ID, but they 93 * may also provide the history of how the rules changed. 94 * 95 * <h3>Specification for implementors</h3> 96 * This interface is a service provider that can be called by multiple threads. 97 * Implementations must be immutable and thread-safe. 98 * <p> 99 * Providers must ensure that once a rule has been seen by the application, the 100 * rule must continue to be available. 101 * <p> 102 * Many systems would like to update time-zone rules dynamically without stopping the JVM. 103 * When examined in detail, this is a complex problem. 104 * Providers may choose to handle dynamic updates, however the default provider does not. 105 * 106 * @since 1.8 107 */ 108 public abstract class ZoneRulesProvider { 109 110 /** 111 * The set of loaded providers. 112 */ 113 private static final CopyOnWriteArrayList<ZoneRulesProvider> PROVIDERS = new CopyOnWriteArrayList<>(); 114 /** 115 * The lookup from zone region ID to provider. 116 */ 117 private static final ConcurrentMap<String, ZoneRulesProvider> ZONES = new ConcurrentHashMap<>(512, 0.75f, 2); 118 static { 119 registerProvider(new TzdbZoneRulesProvider()); 120 ServiceLoader<ZoneRulesProvider> sl = ServiceLoader.load(ZoneRulesProvider.class, ClassLoader.getSystemClassLoader()); 121 List<ZoneRulesProvider> loaded = new ArrayList<>(); 122 Iterator<ZoneRulesProvider> it = sl.iterator(); 123 while (it.hasNext()) { 124 ZoneRulesProvider provider; 125 try { 126 provider = it.next(); 127 } catch (ServiceConfigurationError ex) { 128 if (ex.getCause() instanceof SecurityException) { 129 continue; // ignore the security exception, try the next provider 130 } 131 throw ex; 132 } 133 registerProvider0(provider); 134 } 135 // CopyOnWriteList could be slow if lots of providers and each added individually 136 PROVIDERS.addAll(loaded); 137 } 138 139 //------------------------------------------------------------------------- 140 /** 141 * Gets the set of available zone IDs. 142 * <p> 143 * These zone IDs are loaded and available for use by {@code ZoneId}. 144 * 145 * @return a modifiable copy of the set of zone IDs, not null 146 */ 147 public static Set<String> getAvailableZoneIds() { 148 return new HashSet<>(ZONES.keySet()); 149 } 150 151 /** 152 * Gets the rules for the zone ID. 153 * <p> 154 * This returns the latest available rules for the zone ID. 155 * <p> 156 * This method relies on time-zone data provider files that are configured. 157 * These are loaded using a {@code ServiceLoader}. 158 * 159 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 160 * @return the rules for the ID, not null 161 * @throws ZoneRulesException if the zone ID is unknown 162 */ 163 public static ZoneRules getRules(String zoneId) { 164 Objects.requireNonNull(zoneId, "zoneId"); 165 return getProvider(zoneId).provideRules(zoneId); 166 } 167 168 /** 169 * Gets the history of rules for the zone ID. 170 * <p> 171 * Time-zones are defined by governments and change frequently. 172 * This method allows applications to find the history of changes to the 173 * rules for a single zone ID. The map is keyed by a string, which is the 174 * version string associated with the rules. 175 * <p> 176 * The exact meaning and format of the version is provider specific. 177 * The version must follow lexicographical order, thus the returned map will 178 * be order from the oldest known rules to the newest available rules. 179 * The default 'TZDB' group uses version numbering consisting of the year 180 * followed by a letter, such as '2009e' or '2012f'. 181 * <p> 182 * Implementations must provide a result for each valid zone ID, however 183 * they do not have to provide a history of rules. 184 * Thus the map will always contain one element, and will only contain more 185 * than one element if historical rule information is available. 186 * 187 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 188 * @return a modifiable copy of the history of the rules for the ID, sorted 189 * from oldest to newest, not null 190 * @throws ZoneRulesException if the zone ID is unknown 191 */ 192 public static NavigableMap<String, ZoneRules> getVersions(String zoneId) { 193 Objects.requireNonNull(zoneId, "zoneId"); 194 return getProvider(zoneId).provideVersions(zoneId); 195 } 196 197 /** 198 * Gets the provider for the zone ID. 199 * 200 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 201 * @return the provider, not null 202 * @throws ZoneRulesException if the zone ID is unknown 203 */ 204 private static ZoneRulesProvider getProvider(String zoneId) { 205 ZoneRulesProvider provider = ZONES.get(zoneId); 206 if (provider == null) { 207 if (ZONES.isEmpty()) { 208 throw new ZoneRulesException("No time-zone data files registered"); 209 } 210 throw new ZoneRulesException("Unknown time-zone ID: " + zoneId); 211 } 212 return provider; 213 } 214 215 //------------------------------------------------------------------------- 216 /** 217 * Registers a zone rules provider. 218 * <p> 219 * This adds a new provider to those currently available. 220 * A provider supplies rules for one or more zone IDs. 221 * A provider cannot be registered if it supplies a zone ID that has already been 222 * registered. See the notes on time-zone IDs in {@link ZoneId}, especially 223 * the section on using the concept of a "group" to make IDs unique. 224 * <p> 225 * To ensure the integrity of time-zones already created, there is no way 226 * to deregister providers. 227 * 228 * @param provider the provider to register, not null 229 * @throws ZoneRulesException if a region is already registered 230 */ 231 public static void registerProvider(ZoneRulesProvider provider) { 232 Objects.requireNonNull(provider, "provider"); 233 registerProvider0(provider); 234 PROVIDERS.add(provider); 235 } 236 237 /** 238 * Registers the provider. 239 * 240 * @param provider the provider to register, not null 241 * @throws ZoneRulesException if unable to complete the registration 242 */ 243 private static void registerProvider0(ZoneRulesProvider provider) { 244 for (String zoneId : provider.provideZoneIds()) { 245 Objects.requireNonNull(zoneId, "zoneId"); 246 ZoneRulesProvider old = ZONES.putIfAbsent(zoneId, provider.provideBind(zoneId)); 247 if (old != null) { 248 throw new ZoneRulesException( 249 "Unable to register zone as one already registered with that ID: " + zoneId + 250 ", currently loading from provider: " + provider); 251 } 252 } 253 } 254 255 //------------------------------------------------------------------------- 256 /** 257 * Refreshes the rules from the underlying data provider. 258 * <p> 259 * This method is an extension point that allows providers to refresh their 260 * rules dynamically at a time of the applications choosing. 261 * After calling this method, the offset stored in any {@link ZonedDateTime} 262 * may be invalid for the zone ID. 263 * <p> 264 * Dynamic behavior is entirely optional and most providers, including the 265 * default provider, do not support it. 266 * 267 * @return true if the rules were updated 268 * @throws ZoneRulesException if an error occurs during the refresh 269 */ 270 public static boolean refresh() { 271 boolean changed = false; 272 for (ZoneRulesProvider provider : PROVIDERS) { 273 changed |= provider.provideRefresh(); 274 } 275 return changed; 276 } 277 278 //----------------------------------------------------------------------- 279 /** 280 * Constructor. 281 */ 282 protected ZoneRulesProvider() { 283 } 284 285 //----------------------------------------------------------------------- 286 /** 287 * SPI method to get the available zone IDs. 288 * <p> 289 * This obtains the IDs that this {@code ZoneRulesProvider} provides. 290 * A provider should provide data for at least one region. 291 * <p> 292 * The returned regions remain available and valid for the lifetime of the application. 293 * A dynamic provider may increase the set of regions as more data becomes available. 294 * 295 * @return the unmodifiable set of region IDs being provided, not null 296 */ 297 protected abstract Set<String> provideZoneIds(); 298 299 /** 300 * SPI method to bind to the specified zone ID. 301 * <p> 302 * {@code ZoneRulesProvider} has a lookup from zone ID to provider. 303 * This method is used when building that lookup, allowing providers 304 * to insert a derived provider that is precisely tuned to the zone ID. 305 * This replaces two hash map lookups by one, enhancing performance. 306 * <p> 307 * This optimization is optional. Returning {@code this} is acceptable. 308 * <p> 309 * This implementation creates a bound provider that caches the 310 * rules from the underlying provider. The request to version history 311 * is forward on to the underlying. This is suitable for providers that 312 * cannot change their contents during the lifetime of the JVM. 313 * 314 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 315 * @return the resolved provider for the ID, not null 316 * @throws DateTimeException if there is no provider for the specified group 317 */ 318 protected ZoneRulesProvider provideBind(String zoneId) { 319 return new BoundProvider(this, zoneId); 320 } 321 322 /** 323 * SPI method to get the rules for the zone ID. 324 * <p> 325 * This loads the rules for the region and version specified. 326 * The version may be null to indicate the "latest" version. 327 * 328 * @param regionId the time-zone region ID, not null 329 * @return the rules, not null 330 * @throws DateTimeException if rules cannot be obtained 331 */ 332 protected abstract ZoneRules provideRules(String regionId); 333 334 /** 335 * SPI method to get the history of rules for the zone ID. 336 * <p> 337 * This returns a map of historical rules keyed by a version string. 338 * The exact meaning and format of the version is provider specific. 339 * The version must follow lexicographical order, thus the returned map will 340 * be order from the oldest known rules to the newest available rules. 341 * The default 'TZDB' group uses version numbering consisting of the year 342 * followed by a letter, such as '2009e' or '2012f'. 343 * <p> 344 * Implementations must provide a result for each valid zone ID, however 345 * they do not have to provide a history of rules. 346 * Thus the map will always contain one element, and will only contain more 347 * than one element if historical rule information is available. 348 * <p> 349 * The returned versions remain available and valid for the lifetime of the application. 350 * A dynamic provider may increase the set of versions as more data becomes available. 351 * 352 * @param zoneId the zone region ID as used by {@code ZoneId}, not null 353 * @return a modifiable copy of the history of the rules for the ID, sorted 354 * from oldest to newest, not null 355 * @throws ZoneRulesException if the zone ID is unknown 356 */ 357 protected abstract NavigableMap<String, ZoneRules> provideVersions(String zoneId); 358 359 /** 360 * SPI method to refresh the rules from the underlying data provider. 361 * <p> 362 * This method provides the opportunity for a provider to dynamically 363 * recheck the underlying data provider to find the latest rules. 364 * This could be used to load new rules without stopping the JVM. 365 * Dynamic behavior is entirely optional and most providers do not support it. 366 * <p> 367 * This implementation returns false. 368 * 369 * @return true if the rules were updated 370 * @throws DateTimeException if an error occurs during the refresh 371 */ 372 protected boolean provideRefresh() { 373 return false; 374 } 375 376 //------------------------------------------------------------------------- 377 /** 378 * A provider bound to a single zone ID. 379 */ 380 private static class BoundProvider extends ZoneRulesProvider { 381 private final ZoneRulesProvider provider; 382 private final String zoneId; 383 private final ZoneRules rules; 384 385 private BoundProvider(ZoneRulesProvider provider, String zoneId) { 386 this.provider = provider; 387 this.zoneId = zoneId; 388 this.rules = provider.provideRules(zoneId); 389 } 390 391 @Override 392 protected Set<String> provideZoneIds() { 393 return new HashSet<>(Collections.singleton(zoneId)); 394 } 395 396 @Override 397 protected ZoneRules provideRules(String regionId) { 398 return rules; 399 } 400 401 @Override 402 protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) { 403 return provider.provideVersions(zoneId); 404 } 405 406 @Override 407 public String toString() { 408 return zoneId; 409 } 410 } 411 412 }