jdk/src/share/classes/java/time/zone/ZoneRulesProvider.java
changeset 15658 55b829ca2334
parent 15289 3ac550392e43
child 16852 60207b2b4b42
--- a/jdk/src/share/classes/java/time/zone/ZoneRulesProvider.java	Tue Feb 12 16:02:14 2013 +0400
+++ b/jdk/src/share/classes/java/time/zone/ZoneRulesProvider.java	Tue Feb 12 09:25:43 2013 -0800
@@ -61,11 +61,11 @@
  */
 package java.time.zone;
 
-import java.time.DateTimeException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -85,6 +85,25 @@
  * The static methods provide the public API that can be used to manage the providers.
  * The abstract methods provide the SPI that allows rules to be provided.
  * <p>
+ * ZoneRulesProvider may be installed in an instance of the Java Platform as
+ * extension classes, that is, jar files placed into any of the usual extension
+ * directories. Installed providers are loaded using the service-provider loading
+ * facility defined by the {@link ServiceLoader} class. A ZoneRulesProvider
+ * identifies itself with a provider configuration file named
+ * {@code java.time.zone.ZoneRulesProvider} in the resource directory
+ * {@code META-INF/services}. The file should contain a line that specifies the
+ * fully qualified concrete zonerules-provider class name.
+ * Providers may also be made available by adding them to the class path or by
+ * registering themselves via {@link #registerProvider} method.
+ * <p>
+ * The Java virtual machine has a default provider that provides zone rules
+ * for the time-zones defined by IANA Time Zone Database (TZDB). If the system
+ * property {@code java.time.zone.DefaultZoneRulesProvider} is defined then
+ * it is taken to be the fully-qualified name of a concrete ZoneRulesProvider
+ * class to be loaded as the default provider, using the system class loader.
+ * If this system property is not defined, a system-default provider will be
+ * loaded to serve as the default provider.
+ * <p>
  * Rules are looked up primarily by zone ID, as used by {@link ZoneId}.
  * Only zone region IDs may be used, zone offset IDs are not used here.
  * <p>
@@ -99,6 +118,8 @@
  * Providers must ensure that once a rule has been seen by the application, the
  * rule must continue to be available.
  * <p>
+*  Providers are encouraged to implement a meaningful {@code toString} method.
+ * <p>
  * Many systems would like to update time-zone rules dynamically without stopping the JVM.
  * When examined in detail, this is a complex problem.
  * Providers may choose to handle dynamic updates, however the default provider does not.
@@ -112,13 +133,34 @@
      */
     private static final CopyOnWriteArrayList<ZoneRulesProvider> PROVIDERS = new CopyOnWriteArrayList<>();
     /**
-     * The lookup from zone region ID to provider.
+     * The lookup from zone ID to provider.
      */
     private static final ConcurrentMap<String, ZoneRulesProvider> ZONES = new ConcurrentHashMap<>(512, 0.75f, 2);
+
     static {
-        registerProvider(new TzdbZoneRulesProvider());
+        // if the property java.time.zone.DefaultZoneRulesProvider is
+        // set then its value is the class name of the default provider
+        final List<ZoneRulesProvider> loaded = new ArrayList<>();
+        AccessController.doPrivileged(new PrivilegedAction() {
+            public Object run() {
+                String prop = System.getProperty("java.time.zone.DefaultZoneRulesProvider");
+                if (prop != null) {
+                    try {
+                        Class<?> c = Class.forName(prop, true, ClassLoader.getSystemClassLoader());
+                        ZoneRulesProvider provider = ZoneRulesProvider.class.cast(c.newInstance());
+                        registerProvider(provider);
+                        loaded.add(provider);
+                    } catch (Exception x) {
+                        throw new Error(x);
+                    }
+                } else {
+                    registerProvider(new TzdbZoneRulesProvider());
+                }
+                return null;
+            }
+        });
+
         ServiceLoader<ZoneRulesProvider> sl = ServiceLoader.load(ZoneRulesProvider.class, ClassLoader.getSystemClassLoader());
-        List<ZoneRulesProvider> loaded = new ArrayList<>();
         Iterator<ZoneRulesProvider> it = sl.iterator();
         while (it.hasNext()) {
             ZoneRulesProvider provider;
@@ -130,7 +172,16 @@
                 }
                 throw ex;
             }
-            registerProvider0(provider);
+            boolean found = false;
+            for (ZoneRulesProvider p : loaded) {
+                if (p.getClass() == provider.getClass()) {
+                    found = true;
+                }
+            }
+            if (!found) {
+                registerProvider0(provider);
+                loaded.add(provider);
+            }
         }
         // CopyOnWriteList could be slow if lots of providers and each added individually
         PROVIDERS.addAll(loaded);
@@ -140,7 +191,7 @@
     /**
      * Gets the set of available zone IDs.
      * <p>
-     * These zone IDs are loaded and available for use by {@code ZoneId}.
+     * These IDs are the string form of a {@link ZoneId}.
      *
      * @return a modifiable copy of the set of zone IDs, not null
      */
@@ -155,14 +206,25 @@
      * <p>
      * This method relies on time-zone data provider files that are configured.
      * These are loaded using a {@code ServiceLoader}.
+     * <p>
+     * The caching flag is designed to allow provider implementations to
+     * prevent the rules being cached in {@code ZoneId}.
+     * Under normal circumstances, the caching of zone rules is highly desirable
+     * as it will provide greater performance. However, there is a use case where
+     * the caching would not be desirable, see {@link #provideRules}.
      *
-     * @param zoneId  the zone region ID as used by {@code ZoneId}, not null
-     * @return the rules for the ID, not null
-     * @throws ZoneRulesException if the zone ID is unknown
+     * @param zoneId the zone ID as defined by {@code ZoneId}, not null
+     * @param forCaching whether the rules are being queried for caching,
+     * true if the returned rules will be cached by {@code ZoneId},
+     * false if they will be returned to the user without being cached in {@code ZoneId}
+     * @return the rules, null if {@code forCaching} is true and this
+     * is a dynamic provider that wants to prevent caching in {@code ZoneId},
+     * otherwise not null
+     * @throws ZoneRulesException if rules cannot be obtained for the zone ID
      */
-    public static ZoneRules getRules(String zoneId) {
+    public static ZoneRules getRules(String zoneId, boolean forCaching) {
         Objects.requireNonNull(zoneId, "zoneId");
-        return getProvider(zoneId).provideRules(zoneId);
+        return getProvider(zoneId).provideRules(zoneId, forCaching);
     }
 
     /**
@@ -184,10 +246,10 @@
      * Thus the map will always contain one element, and will only contain more
      * than one element if historical rule information is available.
      *
-     * @param zoneId  the zone region ID as used by {@code ZoneId}, not null
+     * @param zoneId  the zone ID as defined by {@code ZoneId}, not null
      * @return a modifiable copy of the history of the rules for the ID, sorted
      *  from oldest to newest, not null
-     * @throws ZoneRulesException if the zone ID is unknown
+     * @throws ZoneRulesException if history cannot be obtained for the zone ID
      */
     public static NavigableMap<String, ZoneRules> getVersions(String zoneId) {
         Objects.requireNonNull(zoneId, "zoneId");
@@ -197,7 +259,7 @@
     /**
      * Gets the provider for the zone ID.
      *
-     * @param zoneId  the zone region ID as used by {@code ZoneId}, not null
+     * @param zoneId  the zone ID as defined by {@code ZoneId}, not null
      * @return the provider, not null
      * @throws ZoneRulesException if the zone ID is unknown
      */
@@ -226,7 +288,7 @@
      * to deregister providers.
      *
      * @param provider  the provider to register, not null
-     * @throws ZoneRulesException if a region is already registered
+     * @throws ZoneRulesException if a zone ID is already registered
      */
     public static void registerProvider(ZoneRulesProvider provider) {
         Objects.requireNonNull(provider, "provider");
@@ -243,7 +305,7 @@
     private static void registerProvider0(ZoneRulesProvider provider) {
         for (String zoneId : provider.provideZoneIds()) {
             Objects.requireNonNull(zoneId, "zoneId");
-            ZoneRulesProvider old = ZONES.putIfAbsent(zoneId, provider.provideBind(zoneId));
+            ZoneRulesProvider old = ZONES.putIfAbsent(zoneId, provider);
             if (old != null) {
                 throw new ZoneRulesException(
                     "Unable to register zone as one already registered with that ID: " + zoneId +
@@ -252,17 +314,25 @@
         }
     }
 
-    //-------------------------------------------------------------------------
     /**
      * Refreshes the rules from the underlying data provider.
      * <p>
-     * This method is an extension point that allows providers to refresh their
-     * rules dynamically at a time of the applications choosing.
+     * This method allows an application to request that the providers check
+     * for any updates to the provided rules.
      * After calling this method, the offset stored in any {@link ZonedDateTime}
      * may be invalid for the zone ID.
      * <p>
-     * Dynamic behavior is entirely optional and most providers, including the
-     * default provider, do not support it.
+     * Dynamic update of rules is a complex problem and most applications
+     * should not use this method or dynamic rules.
+     * To achieve dynamic rules, a provider implementation will have to be written
+     * as per the specification of this class.
+     * In addition, instances of {@code ZoneRules} must not be cached in the
+     * application as they will become stale. However, the boolean flag on
+     * {@link #provideRules(String, boolean)} allows provider implementations
+     * to control the caching of {@code ZoneId}, potentially ensuring that
+     * all objects in the system see the new rules.
+     * Note that there is likely to be a cost in performance of a dynamic rules
+     * provider. Note also that no dynamic rules provider is in this specification.
      *
      * @return true if the rules were updated
      * @throws ZoneRulesException if an error occurs during the refresh
@@ -275,7 +345,6 @@
         return changed;
     }
 
-    //-----------------------------------------------------------------------
     /**
      * Constructor.
      */
@@ -287,49 +356,43 @@
      * SPI method to get the available zone IDs.
      * <p>
      * This obtains the IDs that this {@code ZoneRulesProvider} provides.
-     * A provider should provide data for at least one region.
+     * A provider should provide data for at least one zone ID.
      * <p>
-     * The returned regions remain available and valid for the lifetime of the application.
-     * A dynamic provider may increase the set of regions as more data becomes available.
+     * The returned zone IDs remain available and valid for the lifetime of the application.
+     * A dynamic provider may increase the set of IDs as more data becomes available.
      *
-     * @return the unmodifiable set of region IDs being provided, not null
+     * @return the set of zone IDs being provided, not null
+     * @throws ZoneRulesException if a problem occurs while providing the IDs
      */
     protected abstract Set<String> provideZoneIds();
 
     /**
-     * SPI method to bind to the specified zone ID.
-     * <p>
-     * {@code ZoneRulesProvider} has a lookup from zone ID to provider.
-     * This method is used when building that lookup, allowing providers
-     * to insert a derived provider that is precisely tuned to the zone ID.
-     * This replaces two hash map lookups by one, enhancing performance.
-     * <p>
-     * This optimization is optional. Returning {@code this} is acceptable.
-     * <p>
-     * This implementation creates a bound provider that caches the
-     * rules from the underlying provider. The request to version history
-     * is forward on to the underlying. This is suitable for providers that
-     * cannot change their contents during the lifetime of the JVM.
-     *
-     * @param zoneId  the zone region ID as used by {@code ZoneId}, not null
-     * @return the resolved provider for the ID, not null
-     * @throws DateTimeException if there is no provider for the specified group
-     */
-    protected ZoneRulesProvider provideBind(String zoneId) {
-        return new BoundProvider(this, zoneId);
-    }
-
-    /**
      * SPI method to get the rules for the zone ID.
      * <p>
-     * This loads the rules for the region and version specified.
-     * The version may be null to indicate the "latest" version.
+     * This loads the rules for the specified zone ID.
+     * The provider implementation must validate that the zone ID is valid and
+     * available, throwing a {@code ZoneRulesException} if it is not.
+     * The result of the method in the valid case depends on the caching flag.
+     * <p>
+     * If the provider implementation is not dynamic, then the result of the
+     * method must be the non-null set of rules selected by the ID.
+     * <p>
+     * If the provider implementation is dynamic, then the flag gives the option
+     * of preventing the returned rules from being cached in {@link ZoneId}.
+     * When the flag is true, the provider is permitted to return null, where
+     * null will prevent the rules from being cached in {@code ZoneId}.
+     * When the flag is false, the provider must return non-null rules.
      *
-     * @param regionId  the time-zone region ID, not null
-     * @return the rules, not null
-     * @throws DateTimeException if rules cannot be obtained
+     * @param zoneId the zone ID as defined by {@code ZoneId}, not null
+     * @param forCaching whether the rules are being queried for caching,
+     * true if the returned rules will be cached by {@code ZoneId},
+     * false if they will be returned to the user without being cached in {@code ZoneId}
+     * @return the rules, null if {@code forCaching} is true and this
+     * is a dynamic provider that wants to prevent caching in {@code ZoneId},
+     * otherwise not null
+     * @throws ZoneRulesException if rules cannot be obtained for the zone ID
      */
-    protected abstract ZoneRules provideRules(String regionId);
+    protected abstract ZoneRules provideRules(String zoneId, boolean forCaching);
 
     /**
      * SPI method to get the history of rules for the zone ID.
@@ -343,16 +406,16 @@
      * <p>
      * Implementations must provide a result for each valid zone ID, however
      * they do not have to provide a history of rules.
-     * Thus the map will always contain one element, and will only contain more
-     * than one element if historical rule information is available.
+     * Thus the map will contain at least one element, and will only contain
+     * more than one element if historical rule information is available.
      * <p>
      * The returned versions remain available and valid for the lifetime of the application.
      * A dynamic provider may increase the set of versions as more data becomes available.
      *
-     * @param zoneId  the zone region ID as used by {@code ZoneId}, not null
+     * @param zoneId  the zone ID as defined by {@code ZoneId}, not null
      * @return a modifiable copy of the history of the rules for the ID, sorted
      *  from oldest to newest, not null
-     * @throws ZoneRulesException if the zone ID is unknown
+     * @throws ZoneRulesException if history cannot be obtained for the zone ID
      */
     protected abstract NavigableMap<String, ZoneRules> provideVersions(String zoneId);
 
@@ -367,46 +430,10 @@
      * This implementation returns false.
      *
      * @return true if the rules were updated
-     * @throws DateTimeException if an error occurs during the refresh
+     * @throws ZoneRulesException if an error occurs during the refresh
      */
     protected boolean provideRefresh() {
         return false;
     }
 
-    //-------------------------------------------------------------------------
-    /**
-     * A provider bound to a single zone ID.
-     */
-    private static class BoundProvider extends ZoneRulesProvider {
-        private final ZoneRulesProvider provider;
-        private final String zoneId;
-        private final ZoneRules rules;
-
-        private BoundProvider(ZoneRulesProvider provider, String zoneId) {
-            this.provider = provider;
-            this.zoneId = zoneId;
-            this.rules = provider.provideRules(zoneId);
-        }
-
-        @Override
-        protected Set<String> provideZoneIds() {
-            return new HashSet<>(Collections.singleton(zoneId));
-        }
-
-        @Override
-        protected ZoneRules provideRules(String regionId) {
-            return rules;
-        }
-
-        @Override
-        protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
-            return provider.provideVersions(zoneId);
-        }
-
-        @Override
-        public String toString() {
-            return zoneId;
-        }
-    }
-
 }