New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8231640: (prop) Canonical property storage #5372
Changes from 2 commits
85748cf
641864e
64e2065
1ded17f
867ec99
848ded8
5326b7c
7736a8f
c9d3cb8
a29d0f0
a9b71d2
06ff3bd
1d24a3a
c1dfb18
ff34ad5
6447f9b
9237466
315f3c8
14711a9
6f5f1be
7098a2c
79d1052
e2effb9
eb31d28
458c1fd
e350721
ed5c221
944cbf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
@@ -41,8 +41,13 @@ | ||
import java.nio.charset.Charset; | ||
import java.nio.charset.IllegalCharsetNameException; | ||
import java.nio.charset.UnsupportedCharsetException; | ||
import java.text.DateFormat; | ||
import java.text.SimpleDateFormat; | ||
import java.security.AccessController; | ||
import java.security.PrivilegedAction; | ||
import java.time.DateTimeException; | ||
import java.time.Instant; | ||
import java.time.ZoneOffset; | ||
import java.time.ZonedDateTime; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.function.BiConsumer; | ||
import java.util.function.BiFunction; | ||
@@ -167,10 +172,41 @@ public class Properties extends Hashtable<Object,Object> { | ||
*/ | ||
private transient volatile ConcurrentHashMap<Object, Object> map; | ||
|
||
// cached, string representation of any Date parsed out of the SOURCE_DATE_EPOCH environment variable | ||
private static volatile String cachedDateComment; | ||
// true if SOURCE_DATE_EPOCH environment variable value has been parsed | ||
private static volatile boolean sourceDateEpochParsed; | ||
@SuppressWarnings("removal") | ||
private static final class LazyDateCommentProvider { | ||
// formatter used while writing out current date. this formatter matches the format | ||
// used by java.util.Date.toString() | ||
private static final DateTimeFormatter currentDateFormatter = | ||
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy"); | ||
private static final String cachedDateComment; | ||
|
||
static { | ||
String sourceDateEpoch = System.getSecurityManager() == null | ||
? System.getenv("SOURCE_DATE_EPOCH") | ||
: AccessController.doPrivileged((PrivilegedAction<String>) | ||
() -> System.getenv("SOURCE_DATE_EPOCH")); | ||
String dateComment = null; | ||
if (sourceDateEpoch != null) { | ||
try { | ||
long epochSeconds = Long.parseLong(sourceDateEpoch); | ||
dateComment = "#" + DateTimeFormatter.RFC_1123_DATE_TIME | ||
.withLocale(Locale.ROOT) | ||
.withZone(ZoneOffset.UTC) | ||
.format(Instant.ofEpochSecond(epochSeconds)); | ||
} catch (NumberFormatException | DateTimeException e) { | ||
// ignore any value that cannot be parsed for the SOURCE_DATE_EPOCH. | ||
// store APIs will subsequently use current date, in their date comments | ||
} | ||
} | ||
cachedDateComment = dateComment; | ||
} | ||
|
||
private static String getDateComment() { | ||
return cachedDateComment != null | ||
? cachedDateComment | ||
: "#" + currentDateFormatter.format(ZonedDateTime.now()); | ||
} | ||
} | ||
|
||
/** | ||
* Creates an empty property list with no default values. | ||
@@ -920,9 +956,17 @@ private void store0(BufferedWriter bw, String comments, boolean escUnicode) | ||
if (comments != null) { | ||
writeComments(bw, comments); | ||
} | ||
writeDateComment(bw); | ||
bw.write(LazyDateCommentProvider.getDateComment()); | ||
bw.newLine(); | ||
synchronized (this) { | ||
for (Map.Entry<Object, Object> e : new TreeMap<>(map).entrySet()) { | ||
var entries = map.entrySet().toArray(new Map.Entry<?, ?>[0]); | ||
Arrays.sort(entries, new Comparator<Map.Entry<?, ?>>() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part here, intentionally doesn't use a lambda, since from what I remember seeing in some mail discussion, it was suggested that using lambda in core parts which get used very early during JVM boostrap, should be avoided. If that's not a concern here, do let me know and I can change it to a lambda. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a fair concern, but writing out a properties file is a pretty high-level operation that doesn't seem likely to be used during bootstrap. Also, I observe that the Assuming we're ok with lambdas, it might be easier to use collections instead of arrays in order to preserve generic types. Unfortunately the map is Something like this might work:
|
||
@Override | ||
public int compare(Map.Entry<?, ?> o1, Map.Entry<?, ?> o2) { | ||
return ((String) o1.getKey()).compareTo((String) o2.getKey()); | ||
} | ||
}); | ||
for (Map.Entry<?, ?> e : entries) { | ||
String key = (String)e.getKey(); | ||
String val = (String)e.getValue(); | ||
key = saveConvert(key, true, escUnicode); | ||
@@ -937,59 +981,6 @@ private void store0(BufferedWriter bw, String comments, boolean escUnicode) | ||
bw.flush(); | ||
} | ||
|
||
private static void writeDateComment(BufferedWriter bw) throws IOException { | ||
if (sourceDateEpochParsed && cachedDateComment == null) { | ||
// SOURCE_DATE_EPOCH environment variable value has already been queried previously and | ||
// the environment variable was either not set or its value couldn't be parsed to a Date. | ||
// In either case, we write out the current date in the date comment | ||
bw.write("#" + new Date()); | ||
bw.newLine(); | ||
return; | ||
} | ||
// Either the SOURCE_DATE_EPOCH environment variable needs to be queried or we have already | ||
// queried it previously and are holding a cached string representation of that value. | ||
// In either case, we first make sure the current caller has the necessary permissions to access | ||
// that environment variable's value | ||
String sourceDateEpoch = null; | ||
try { | ||
sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH"); | ||
} catch (SecurityException se) { | ||
// caller code doesn't have permissions to SOURCE_DATE_EPOCH environment variable. | ||
// Use current date in comment | ||
bw.write("#" + new Date()); | ||
bw.newLine(); | ||
return; | ||
} | ||
// caller code has permissions to the environment variable, OK to use (any parseable) value | ||
// of that environment variable in the date comment | ||
if (!sourceDateEpochParsed) { | ||
synchronized (Properties.class) { | ||
if (!sourceDateEpochParsed) { | ||
try { | ||
String dateComment = null; | ||
if (sourceDateEpoch != null) { | ||
try { | ||
Date d = new Date(Long.parseLong(sourceDateEpoch) * 1000); | ||
// use the same format as that of Date.toGMTString() and a neutral locale for reproducibility | ||
DateFormat df = new SimpleDateFormat("d MMM yyyy HH:mm:ss 'GMT'", Locale.ROOT); | ||
df.setTimeZone(TimeZone.getTimeZone("GMT")); | ||
dateComment = "#" + df.format(d); | ||
} catch (NumberFormatException nfe) { | ||
// ignore any value that cannot be parsed for the SOURCE_DATE_EPOCH. | ||
// store APIs will subsequently use current date, in their date comments | ||
} | ||
} | ||
cachedDateComment = dateComment; | ||
} finally { | ||
sourceDateEpochParsed = true; | ||
} | ||
} | ||
} | ||
} | ||
bw.write(cachedDateComment != null ? cachedDateComment : "#" + new Date()); | ||
bw.newLine(); | ||
} | ||
|
||
/** | ||
* Loads all of the properties represented by the XML document on the | ||
* specified input stream into this properties table. | ||
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format of SOURCE_DATE_EPOCH is fine, but referring to an external document as part of the OpenJDK spec is undesirable in the long run. Previous comments gave support to using the environment variable directly but
it very unlike similar cases that use system properties when the behavior of an API needs to be overridden or modified.
As a system property, specified on the command line, it would be visible when the program is invoked,
explicitly intended to change behavior and not having a context sensitive effect. Named perhaps, java.util.Properties.storeDate.
It can be documented as part of the spec with the javadoc tag "{@systemProperty ...}"
There is a cache of properties in jdk.internal.util.StaticProperty that captures the state when the runtime is initialized. For specific properties, they are cached and available without regard to the setting of the SecurityManager or not. (BTW, there is no need to special case doPriv calls, they are pretty well optimized when the security manager is not set and it keeps the code simpler.)
Given the low frequency of calls to store(), even caching the parsed date doesn't save much.
And the cost is the cost of the size and loading an extra class.