8187987: Add a mechanism to configure custom variants in HijrahChronology

Reviewed-by: joehw, rriggs
This commit is contained in:
Naoto Sato 2020-01-21 08:02:10 -08:00
parent f129cc4328
commit 0ae7207e95
8 changed files with 266 additions and 12 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -60,10 +60,15 @@ package java.time.chrono;
import static java.time.temporal.ChronoField.EPOCH_DAY;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.Clock;
@ -189,6 +194,11 @@ import sun.util.logging.PlatformLogger;
* </tr>
* </tbody>
* </table>
* <p>
* Additional variants may be added by providing configuration properties files in
* {@code <JAVA_HOME>/conf/chronology} directory. The properties
* files should follow the naming convention of
* {@code hijrah-config-<chronology id>_<calendar type>.properties}.
*
* @since 1.8
*/
@ -278,6 +288,11 @@ public final class HijrahChronology extends AbstractChronology implements Serial
// Register it by its aliases
AbstractChronology.registerChrono(INSTANCE, "Hijrah");
AbstractChronology.registerChrono(INSTANCE, "islamic");
// custom config chronologies
CONF_PATH = Path.of(AccessController.doPrivileged((PrivilegedAction<String>)
() -> System.getProperty("java.home")), "conf", "chronology");
registerCustomChrono();
}
/**
@ -800,27 +815,40 @@ public final class HijrahChronology extends AbstractChronology implements Serial
private static final String KEY_TYPE = "type";
private static final String KEY_VERSION = "version";
private static final String KEY_ISO_START = "iso-start";
private static final Path CONF_PATH;
/**
* Return the configuration properties from the resource.
* <p>
* The location of the variant configuration resource is:
* <pre>
* "/java/time/chrono/hijrah-config-" + calendarType + ".properties"
* "/java/time/chrono/" (for "islamic-umalqura" type), or
* "<JAVA_HOME>/conf/chronology/" +
* "hijrah-config-" + chronologyId + "_" + calendarType + ".properties"
* </pre>
*
* @param chronologyId the chronology ID of the calendar variant
* @param calendarType the calendarType of the calendar variant
* @return a Properties containing the properties read from the resource.
* @throws Exception if access to the property resource fails
*/
private Properties readConfigProperties(final String calendarType) throws Exception {
String resourceName = RESOURCE_PREFIX + calendarType + RESOURCE_SUFFIX;
PrivilegedAction<InputStream> getResourceAction = () -> HijrahChronology.class.getResourceAsStream(resourceName);
private static Properties readConfigProperties(final String chronologyId, final String calendarType) throws Exception {
String resourceName = RESOURCE_PREFIX + chronologyId + "_" + calendarType + RESOURCE_SUFFIX;
PrivilegedAction<InputStream> getResourceAction = calendarType.equals("islamic-umalqura") ?
() -> HijrahChronology.class.getResourceAsStream(resourceName) :
() -> {
try {
return Files.newInputStream(CONF_PATH.resolve(resourceName),
StandardOpenOption.READ);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
FilePermission perm1 = new FilePermission("<<ALL FILES>>", "read");
RuntimePermission perm2 = new RuntimePermission("accessSystemModules");
try (InputStream is = AccessController.doPrivileged(getResourceAction, null, perm1, perm2)) {
if (is == null) {
throw new RuntimeException("Hijrah calendar resource not found: /java/time/chrono/" + resourceName);
throw new RuntimeException("Hijrah calendar resource not found: " + resourceName);
}
Properties props = new Properties();
props.load(is);
@ -841,7 +869,7 @@ public final class HijrahChronology extends AbstractChronology implements Serial
*/
private void loadCalendarData() {
try {
Properties props = readConfigProperties(calendarType);
Properties props = readConfigProperties(typeId, calendarType);
Map<Integer, int[]> years = new HashMap<>();
int minYear = Integer.MAX_VALUE;
@ -1009,6 +1037,44 @@ public final class HijrahChronology extends AbstractChronology implements Serial
}
}
/**
* Look for Hijrah chronology variant properties files in
* <JAVA_HOME>/conf/chronology directory. Then register its chronology, if any.
*/
private static void registerCustomChrono() {
AccessController.doPrivileged(
(PrivilegedAction<Void>)() -> {
if (Files.isDirectory(CONF_PATH)) {
try {
Files.list(CONF_PATH)
.map(p -> p.getFileName().toString())
.filter(fn -> fn.matches("hijrah-config-[^\\.]+\\.properties"))
.map(fn -> fn.replaceAll("(hijrah-config-|\\.properties)", ""))
.forEach(idtype -> {
int delimiterPos = idtype.indexOf('_');
// '_' should be somewhere in the middle of idtype
if (delimiterPos > 1 && delimiterPos < idtype.length() - 1) {
AbstractChronology.registerChrono(
new HijrahChronology(
idtype.substring(0, delimiterPos),
idtype.substring(delimiterPos + 1)));
} else {
PlatformLogger.getLogger("java.time.chrono")
.warning("Hijrah custom config init failed." +
"'<id>_<type>' name convention not followed: " + idtype);
}
});
} catch (IOException e) {
PlatformLogger.getLogger("java.time.chrono")
.warning("Hijrah custom config init failed.", e);
}
}
return null;
},
null,
new FilePermission("<<ALL FILES>>", "read"));
}
//-----------------------------------------------------------------------
/**
* Writes the Chronology using a