Path: blob/master/src/java.base/share/classes/java/time/zone/ZoneRulesProvider.java
41159 views
/*1* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time.zone;6263import java.security.AccessController;64import java.security.PrivilegedAction;65import java.time.ZoneId;66import java.time.ZonedDateTime;67import java.util.ArrayList;68import java.util.HashSet;69import java.util.Iterator;70import java.util.List;71import java.util.NavigableMap;72import java.util.Objects;73import java.util.ServiceConfigurationError;74import java.util.ServiceLoader;75import java.util.Set;76import java.util.concurrent.ConcurrentHashMap;77import java.util.concurrent.ConcurrentMap;78import java.util.concurrent.CopyOnWriteArrayList;79import java.util.Collections;8081/**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* ZoneRulesProvider may be installed in an instance of the Java Platform as89* extension classes, that is, jar files placed into any of the usual extension90* directories. Installed providers are loaded using the service-provider loading91* facility defined by the {@link ServiceLoader} class. A ZoneRulesProvider92* identifies itself with a provider configuration file named93* {@code java.time.zone.ZoneRulesProvider} in the resource directory94* {@code META-INF/services}. The file should contain a line that specifies the95* fully qualified concrete zonerules-provider class name.96* Providers may also be made available by adding them to the class path or by97* registering themselves via {@link #registerProvider} method.98* <p>99* The Java virtual machine has a default provider that provides zone rules100* for the time-zones defined by IANA Time Zone Database (TZDB). If the system101* property {@systemProperty java.time.zone.DefaultZoneRulesProvider} is defined then102* it is taken to be the fully-qualified name of a concrete ZoneRulesProvider103* class to be loaded as the default provider, using the system class loader.104* If this system property is not defined, a system-default provider will be105* loaded to serve as the default provider.106* <p>107* Rules are looked up primarily by zone ID, as used by {@link ZoneId}.108* Only zone region IDs may be used, zone offset IDs are not used here.109* <p>110* Time-zone rules are political, thus the data can change at any time.111* Each provider will provide the latest rules for each zone ID, but they112* may also provide the history of how the rules changed.113*114* @implSpec115* This interface is a service provider that can be called by multiple threads.116* Implementations must be immutable and thread-safe.117* <p>118* Providers must ensure that once a rule has been seen by the application, the119* rule must continue to be available.120* <p>121* Providers are encouraged to implement a meaningful {@code toString} method.122* <p>123* Many systems would like to update time-zone rules dynamically without stopping the JVM.124* When examined in detail, this is a complex problem.125* Providers may choose to handle dynamic updates, however the default provider does not.126*127* @since 1.8128*/129@SuppressWarnings("removal")130public abstract class ZoneRulesProvider {131132/**133* The set of loaded providers.134*/135private static final CopyOnWriteArrayList<ZoneRulesProvider> PROVIDERS = new CopyOnWriteArrayList<>();136/**137* The lookup from zone ID to provider.138*/139private static final ConcurrentMap<String, ZoneRulesProvider> ZONES = new ConcurrentHashMap<>(512, 0.75f, 2);140141/**142* The zone ID data143*/144private static volatile Set<String> ZONE_IDS;145146static {147// if the property java.time.zone.DefaultZoneRulesProvider is148// set then its value is the class name of the default provider149final List<ZoneRulesProvider> loaded = new ArrayList<>();150AccessController.doPrivileged(new PrivilegedAction<>() {151public Object run() {152String prop = System.getProperty("java.time.zone.DefaultZoneRulesProvider");153if (prop != null) {154try {155Class<?> c = Class.forName(prop, true, ClassLoader.getSystemClassLoader());156@SuppressWarnings("deprecation")157ZoneRulesProvider provider = ZoneRulesProvider.class.cast(c.newInstance());158registerProvider(provider);159loaded.add(provider);160} catch (Exception x) {161throw new Error(x);162}163} else {164registerProvider(new TzdbZoneRulesProvider());165}166return null;167}168});169170ServiceLoader<ZoneRulesProvider> sl = ServiceLoader.load(ZoneRulesProvider.class, ClassLoader.getSystemClassLoader());171Iterator<ZoneRulesProvider> it = sl.iterator();172while (it.hasNext()) {173ZoneRulesProvider provider;174try {175provider = it.next();176} catch (ServiceConfigurationError ex) {177if (ex.getCause() instanceof SecurityException) {178continue; // ignore the security exception, try the next provider179}180throw ex;181}182boolean found = false;183for (ZoneRulesProvider p : loaded) {184if (p.getClass() == provider.getClass()) {185found = true;186}187}188if (!found) {189registerProvider0(provider);190loaded.add(provider);191}192}193// CopyOnWriteList could be slow if lots of providers and each added individually194PROVIDERS.addAll(loaded);195}196197//-------------------------------------------------------------------------198/**199* Gets the set of available zone IDs.200* <p>201* These IDs are the string form of a {@link ZoneId}.202*203* @return the unmodifiable set of zone IDs, not null204*/205public static Set<String> getAvailableZoneIds() {206return ZONE_IDS;207}208209/**210* Gets the rules for the zone ID.211* <p>212* This returns the latest available rules for the zone ID.213* <p>214* This method relies on time-zone data provider files that are configured.215* These are loaded using a {@code ServiceLoader}.216* <p>217* The caching flag is designed to allow provider implementations to218* prevent the rules being cached in {@code ZoneId}.219* Under normal circumstances, the caching of zone rules is highly desirable220* as it will provide greater performance. However, there is a use case where221* the caching would not be desirable, see {@link #provideRules}.222*223* @param zoneId the zone ID as defined by {@code ZoneId}, not null224* @param forCaching whether the rules are being queried for caching,225* true if the returned rules will be cached by {@code ZoneId},226* false if they will be returned to the user without being cached in {@code ZoneId}227* @return the rules, null if {@code forCaching} is true and this228* is a dynamic provider that wants to prevent caching in {@code ZoneId},229* otherwise not null230* @throws ZoneRulesException if rules cannot be obtained for the zone ID231*/232public static ZoneRules getRules(String zoneId, boolean forCaching) {233Objects.requireNonNull(zoneId, "zoneId");234return getProvider(zoneId).provideRules(zoneId, forCaching);235}236237/**238* Gets the history of rules for the zone ID.239* <p>240* Time-zones are defined by governments and change frequently.241* This method allows applications to find the history of changes to the242* rules for a single zone ID. The map is keyed by a string, which is the243* version string associated with the rules.244* <p>245* The exact meaning and format of the version is provider specific.246* The version must follow lexicographical order, thus the returned map will247* be order from the oldest known rules to the newest available rules.248* The default 'TZDB' group uses version numbering consisting of the year249* followed by a letter, such as '2009e' or '2012f'.250* <p>251* Implementations must provide a result for each valid zone ID, however252* they do not have to provide a history of rules.253* Thus the map will always contain one element, and will only contain more254* than one element if historical rule information is available.255*256* @param zoneId the zone ID as defined by {@code ZoneId}, not null257* @return a modifiable copy of the history of the rules for the ID, sorted258* from oldest to newest, not null259* @throws ZoneRulesException if history cannot be obtained for the zone ID260*/261public static NavigableMap<String, ZoneRules> getVersions(String zoneId) {262Objects.requireNonNull(zoneId, "zoneId");263return getProvider(zoneId).provideVersions(zoneId);264}265266/**267* Gets the provider for the zone ID.268*269* @param zoneId the zone ID as defined by {@code ZoneId}, not null270* @return the provider, not null271* @throws ZoneRulesException if the zone ID is unknown272*/273private static ZoneRulesProvider getProvider(String zoneId) {274ZoneRulesProvider provider = ZONES.get(zoneId);275if (provider == null) {276if (ZONES.isEmpty()) {277throw new ZoneRulesException("No time-zone data files registered");278}279throw new ZoneRulesException("Unknown time-zone ID: " + zoneId);280}281return provider;282}283284//-------------------------------------------------------------------------285/**286* Registers a zone rules provider.287* <p>288* This adds a new provider to those currently available.289* A provider supplies rules for one or more zone IDs.290* A provider cannot be registered if it supplies a zone ID that has already been291* registered. See the notes on time-zone IDs in {@link ZoneId}, especially292* the section on using the concept of a "group" to make IDs unique.293* <p>294* To ensure the integrity of time-zones already created, there is no way295* to deregister providers.296*297* @param provider the provider to register, not null298* @throws ZoneRulesException if a zone ID is already registered299*/300public static void registerProvider(ZoneRulesProvider provider) {301Objects.requireNonNull(provider, "provider");302registerProvider0(provider);303PROVIDERS.add(provider);304}305306/**307* Registers the provider.308*309* @param provider the provider to register, not null310* @throws ZoneRulesException if unable to complete the registration311*/312private static synchronized void registerProvider0(ZoneRulesProvider provider) {313for (String zoneId : provider.provideZoneIds()) {314Objects.requireNonNull(zoneId, "zoneId");315ZoneRulesProvider old = ZONES.putIfAbsent(zoneId, provider);316if (old != null) {317throw new ZoneRulesException(318"Unable to register zone as one already registered with that ID: " + zoneId +319", currently loading from provider: " + provider);320}321}322Set<String> combinedSet = new HashSet<String>(ZONES.keySet());323ZONE_IDS = Collections.unmodifiableSet(combinedSet);324}325326/**327* Refreshes the rules from the underlying data provider.328* <p>329* This method allows an application to request that the providers check330* for any updates to the provided rules.331* After calling this method, the offset stored in any {@link ZonedDateTime}332* may be invalid for the zone ID.333* <p>334* Dynamic update of rules is a complex problem and most applications335* should not use this method or dynamic rules.336* To achieve dynamic rules, a provider implementation will have to be written337* as per the specification of this class.338* In addition, instances of {@code ZoneRules} must not be cached in the339* application as they will become stale. However, the boolean flag on340* {@link #provideRules(String, boolean)} allows provider implementations341* to control the caching of {@code ZoneId}, potentially ensuring that342* all objects in the system see the new rules.343* Note that there is likely to be a cost in performance of a dynamic rules344* provider. Note also that no dynamic rules provider is in this specification.345*346* @return true if the rules were updated347* @throws ZoneRulesException if an error occurs during the refresh348*/349public static boolean refresh() {350boolean changed = false;351for (ZoneRulesProvider provider : PROVIDERS) {352changed |= provider.provideRefresh();353}354return changed;355}356357/**358* Constructor.359*/360protected ZoneRulesProvider() {361}362363//-----------------------------------------------------------------------364/**365* SPI method to get the available zone IDs.366* <p>367* This obtains the IDs that this {@code ZoneRulesProvider} provides.368* A provider should provide data for at least one zone ID.369* <p>370* The returned zone IDs remain available and valid for the lifetime of the application.371* A dynamic provider may increase the set of IDs as more data becomes available.372*373* @return the set of zone IDs being provided, not null374* @throws ZoneRulesException if a problem occurs while providing the IDs375*/376protected abstract Set<String> provideZoneIds();377378/**379* SPI method to get the rules for the zone ID.380* <p>381* This loads the rules for the specified zone ID.382* The provider implementation must validate that the zone ID is valid and383* available, throwing a {@code ZoneRulesException} if it is not.384* The result of the method in the valid case depends on the caching flag.385* <p>386* If the provider implementation is not dynamic, then the result of the387* method must be the non-null set of rules selected by the ID.388* <p>389* If the provider implementation is dynamic, then the flag gives the option390* of preventing the returned rules from being cached in {@link ZoneId}.391* When the flag is true, the provider is permitted to return null, where392* null will prevent the rules from being cached in {@code ZoneId}.393* When the flag is false, the provider must return non-null rules.394*395* @param zoneId the zone ID as defined by {@code ZoneId}, not null396* @param forCaching whether the rules are being queried for caching,397* true if the returned rules will be cached by {@code ZoneId},398* false if they will be returned to the user without being cached in {@code ZoneId}399* @return the rules, null if {@code forCaching} is true and this400* is a dynamic provider that wants to prevent caching in {@code ZoneId},401* otherwise not null402* @throws ZoneRulesException if rules cannot be obtained for the zone ID403*/404protected abstract ZoneRules provideRules(String zoneId, boolean forCaching);405406/**407* SPI method to get the history of rules for the zone ID.408* <p>409* This returns a map of historical rules keyed by a version string.410* The exact meaning and format of the version is provider specific.411* The version must follow lexicographical order, thus the returned map will412* be order from the oldest known rules to the newest available rules.413* The default 'TZDB' group uses version numbering consisting of the year414* followed by a letter, such as '2009e' or '2012f'.415* <p>416* Implementations must provide a result for each valid zone ID, however417* they do not have to provide a history of rules.418* Thus the map will contain at least one element, and will only contain419* more than one element if historical rule information is available.420* <p>421* The returned versions remain available and valid for the lifetime of the application.422* A dynamic provider may increase the set of versions as more data becomes available.423*424* @param zoneId the zone ID as defined by {@code ZoneId}, not null425* @return a modifiable copy of the history of the rules for the ID, sorted426* from oldest to newest, not null427* @throws ZoneRulesException if history cannot be obtained for the zone ID428*/429protected abstract NavigableMap<String, ZoneRules> provideVersions(String zoneId);430431/**432* SPI method to refresh the rules from the underlying data provider.433* <p>434* This method provides the opportunity for a provider to dynamically435* recheck the underlying data provider to find the latest rules.436* This could be used to load new rules without stopping the JVM.437* Dynamic behavior is entirely optional and most providers do not support it.438* <p>439* This implementation returns false.440*441* @return true if the rules were updated442* @throws ZoneRulesException if an error occurs during the refresh443*/444protected boolean provideRefresh() {445return false;446}447448}449450451