mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-23 04:24:49 +02:00
487 lines
17 KiB
Java
487 lines
17 KiB
Java
/*
|
|
* Copyright (c) 2011, 2016, 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
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package java.util.prefs;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Timer;
|
|
import java.util.TimerTask;
|
|
import java.lang.ref.WeakReference;
|
|
|
|
|
|
/*
|
|
MacOSXPreferencesFile synchronization:
|
|
|
|
Everything is synchronized on MacOSXPreferencesFile.class. This prevents:
|
|
* simultaneous updates to cachedFiles or changedFiles
|
|
* simultaneous creation of two objects for the same name+user+host triplet
|
|
* simultaneous modifications to the same file
|
|
* modifications during syncWorld/flushWorld
|
|
* (in MacOSXPreferences.removeNodeSpi()) modification or sync during
|
|
multi-step node removal process
|
|
... among other things.
|
|
*/
|
|
/*
|
|
Timers. There are two timers that control synchronization of prefs data to
|
|
and from disk.
|
|
|
|
* Sync timer periodically calls syncWorld() to force external disk changes
|
|
(e.g. from another VM) into the memory cache. The sync timer runs even
|
|
if there are no outstanding local changes. The sync timer syncs all live
|
|
MacOSXPreferencesFile objects (the cachedFiles list).
|
|
The sync timer period is controlled by the java.util.prefs.syncInterval
|
|
property (same as FileSystemPreferences). By default there is *no*
|
|
sync timer (unlike FileSystemPreferences); it is only enabled if the
|
|
syncInterval property is set. The minimum interval is 5 seconds.
|
|
|
|
* Flush timer calls flushWorld() to force local changes to disk.
|
|
The flush timer is scheduled to fire some time after each pref change,
|
|
unless it's already scheduled to fire before that. syncWorld and
|
|
flushWorld will cancel any outstanding flush timer as unnecessary.
|
|
The flush timer flushes all changed files (the changedFiles list).
|
|
The time between pref write and flush timer call is controlled by the
|
|
java.util.prefs.flushDelay property (unlike FileSystemPreferences).
|
|
The default is 60 seconds and the minimum is 5 seconds.
|
|
|
|
The flush timer's behavior is required by the Java Preferences spec
|
|
("changes will eventually propagate to the persistent backing store with
|
|
an implementation-dependent delay"). The sync timer is not required by
|
|
the spec (multiple VMs are only required to not corrupt the prefs), but
|
|
the periodic sync is implemented by FileSystemPreferences and may be
|
|
useful to some programs. The sync timer is disabled by default because
|
|
it's expensive and is usually not necessary.
|
|
*/
|
|
|
|
class MacOSXPreferencesFile {
|
|
|
|
static {
|
|
java.security.AccessController.doPrivileged(
|
|
new java.security.PrivilegedAction<Void>() {
|
|
public Void run() {
|
|
System.loadLibrary("prefs");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
private class FlushTask extends TimerTask {
|
|
public void run() {
|
|
MacOSXPreferencesFile.flushWorld();
|
|
}
|
|
}
|
|
|
|
private class SyncTask extends TimerTask {
|
|
public void run() {
|
|
MacOSXPreferencesFile.syncWorld();
|
|
}
|
|
}
|
|
|
|
// Maps string -> weak reference to MacOSXPreferencesFile
|
|
private static HashMap<String, WeakReference<MacOSXPreferencesFile>>
|
|
cachedFiles;
|
|
// Files that may have unflushed changes
|
|
private static HashSet<MacOSXPreferencesFile> changedFiles;
|
|
|
|
|
|
// Timer and pending sync and flush tasks (which are both scheduled
|
|
// on the same timer)
|
|
private static Timer timer = null;
|
|
private static FlushTask flushTimerTask = null;
|
|
private static long flushDelay = -1; // in seconds (min 5, default 60)
|
|
private static long syncInterval = -1; // (min 5, default negative == off)
|
|
|
|
private String appName;
|
|
private long user;
|
|
private long host;
|
|
|
|
String name() { return appName; }
|
|
long user() { return user; }
|
|
long host() { return host; }
|
|
|
|
// private constructor - use factory method getFile() instead
|
|
private MacOSXPreferencesFile(String newName, long newUser, long newHost)
|
|
{
|
|
appName = newName;
|
|
user = newUser;
|
|
host = newHost;
|
|
}
|
|
|
|
// Factory method
|
|
// Always returns the same object for the given name+user+host
|
|
static synchronized MacOSXPreferencesFile
|
|
getFile(String newName, boolean isUser)
|
|
{
|
|
MacOSXPreferencesFile result = null;
|
|
|
|
if (cachedFiles == null)
|
|
cachedFiles = new HashMap<>();
|
|
|
|
String hashkey =
|
|
newName + String.valueOf(isUser);
|
|
WeakReference<MacOSXPreferencesFile> hashvalue = cachedFiles.get(hashkey);
|
|
if (hashvalue != null) {
|
|
result = hashvalue.get();
|
|
}
|
|
if (result == null) {
|
|
// Java user node == CF current user, any host
|
|
// Java system node == CF any user, current host
|
|
result = new MacOSXPreferencesFile(newName,
|
|
isUser ? cfCurrentUser : cfAnyUser,
|
|
isUser ? cfAnyHost : cfCurrentHost);
|
|
cachedFiles.put(hashkey, new WeakReference<MacOSXPreferencesFile>(result));
|
|
}
|
|
|
|
// Don't schedule this file for flushing until some nodes or
|
|
// keys are added to it.
|
|
|
|
// Do set up the sync timer if requested; sync timer affects reads
|
|
// as well as writes.
|
|
initSyncTimerIfNeeded();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// Write all prefs changes to disk and clear all cached prefs values
|
|
// (so the next read will read from disk).
|
|
static synchronized boolean syncWorld()
|
|
{
|
|
boolean ok = true;
|
|
|
|
if (cachedFiles != null && !cachedFiles.isEmpty()) {
|
|
Iterator<WeakReference<MacOSXPreferencesFile>> iter =
|
|
cachedFiles.values().iterator();
|
|
while (iter.hasNext()) {
|
|
WeakReference<MacOSXPreferencesFile> ref = iter.next();
|
|
MacOSXPreferencesFile f = ref.get();
|
|
if (f != null) {
|
|
if (!f.synchronize()) ok = false;
|
|
} else {
|
|
iter.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Kill any pending flush
|
|
if (flushTimerTask != null) {
|
|
flushTimerTask.cancel();
|
|
flushTimerTask = null;
|
|
}
|
|
|
|
// Clear changed file list. The changed files were guaranteed to
|
|
// have been in the cached file list (because there was a strong
|
|
// reference from changedFiles.
|
|
if (changedFiles != null) changedFiles.clear();
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
// Sync only current user preferences
|
|
static synchronized boolean syncUser() {
|
|
boolean ok = true;
|
|
if (cachedFiles != null && !cachedFiles.isEmpty()) {
|
|
Iterator<WeakReference<MacOSXPreferencesFile>> iter =
|
|
cachedFiles.values().iterator();
|
|
while (iter.hasNext()) {
|
|
WeakReference<MacOSXPreferencesFile> ref = iter.next();
|
|
MacOSXPreferencesFile f = ref.get();
|
|
if (f != null && f.user == cfCurrentUser) {
|
|
if (!f.synchronize()) {
|
|
ok = false;
|
|
}
|
|
} else {
|
|
iter.remove();
|
|
}
|
|
}
|
|
}
|
|
// Remove synchronized file from changed file list. The changed files were
|
|
// guaranteed to have been in the cached file list (because there was a strong
|
|
// reference from changedFiles.
|
|
if (changedFiles != null) {
|
|
Iterator<MacOSXPreferencesFile> iterChanged = changedFiles.iterator();
|
|
while (iterChanged.hasNext()) {
|
|
MacOSXPreferencesFile f = iterChanged.next();
|
|
if (f != null && f.user == cfCurrentUser)
|
|
iterChanged.remove();
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//Flush only current user preferences
|
|
static synchronized boolean flushUser() {
|
|
boolean ok = true;
|
|
if (changedFiles != null && !changedFiles.isEmpty()) {
|
|
Iterator<MacOSXPreferencesFile> iterator = changedFiles.iterator();
|
|
while(iterator.hasNext()) {
|
|
MacOSXPreferencesFile f = iterator.next();
|
|
if (f.user == cfCurrentUser) {
|
|
if (!f.synchronize())
|
|
ok = false;
|
|
else
|
|
iterator.remove();
|
|
}
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// Write all prefs changes to disk, but do not clear all cached prefs
|
|
// values. Also kills any scheduled flush task.
|
|
// There's no CFPreferencesFlush() (<rdar://problem/3049129>), so lots of cached prefs
|
|
// are cleared anyway.
|
|
static synchronized boolean flushWorld()
|
|
{
|
|
boolean ok = true;
|
|
|
|
if (changedFiles != null && !changedFiles.isEmpty()) {
|
|
for (MacOSXPreferencesFile f : changedFiles) {
|
|
if (!f.synchronize())
|
|
ok = false;
|
|
}
|
|
changedFiles.clear();
|
|
}
|
|
|
|
if (flushTimerTask != null) {
|
|
flushTimerTask.cancel();
|
|
flushTimerTask = null;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
// Mark this prefs file as changed. The changes will be flushed in
|
|
// at most flushDelay() seconds.
|
|
// Must be called when synchronized on MacOSXPreferencesFile.class
|
|
private void markChanged()
|
|
{
|
|
// Add this file to the changed file list
|
|
if (changedFiles == null)
|
|
changedFiles = new HashSet<>();
|
|
changedFiles.add(this);
|
|
|
|
// Schedule a new flush and a shutdown hook, if necessary
|
|
if (flushTimerTask == null) {
|
|
flushTimerTask = new FlushTask();
|
|
timer().schedule(flushTimerTask, flushDelay() * 1000);
|
|
}
|
|
}
|
|
|
|
// Return the flush delay, initializing from a property if necessary.
|
|
private static synchronized long flushDelay()
|
|
{
|
|
if (flushDelay == -1) {
|
|
try {
|
|
// flush delay >= 5, default 60
|
|
flushDelay = Math.max(5, Integer.parseInt(System.getProperty("java.util.prefs.flushDelay", "60")));
|
|
} catch (NumberFormatException e) {
|
|
flushDelay = 60;
|
|
}
|
|
}
|
|
return flushDelay;
|
|
}
|
|
|
|
// Initialize and run the sync timer, if the sync timer property is set
|
|
// and the sync timer hasn't already been started.
|
|
private static synchronized void initSyncTimerIfNeeded()
|
|
{
|
|
// syncInterval: -1 is uninitialized, other negative is off,
|
|
// positive is seconds between syncs (min 5).
|
|
|
|
if (syncInterval == -1) {
|
|
try {
|
|
syncInterval = Integer.parseInt(System.getProperty("java.util.prefs.syncInterval", "-2"));
|
|
if (syncInterval >= 0) {
|
|
// minimum of 5 seconds
|
|
syncInterval = Math.max(5, syncInterval);
|
|
} else {
|
|
syncInterval = -2; // default off
|
|
}
|
|
} catch (NumberFormatException e) {
|
|
syncInterval = -2; // bad property value - default off
|
|
}
|
|
|
|
if (syncInterval > 0) {
|
|
timer().schedule(new TimerTask() {
|
|
@Override
|
|
public void run() {
|
|
MacOSXPreferencesFile.syncWorld();}
|
|
}, syncInterval * 1000, syncInterval * 1000);
|
|
} else {
|
|
// syncInterval property not set. No sync timer ever.
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the timer used for flush and sync, creating it if necessary.
|
|
private static synchronized Timer timer()
|
|
{
|
|
if (timer == null) {
|
|
timer = new Timer(true); // daemon
|
|
Thread flushThread =
|
|
new Thread(null, null, "Flush Thread", 0, false) {
|
|
@Override
|
|
public void run() {
|
|
flushWorld();
|
|
}
|
|
};
|
|
/* Set context class loader to null in order to avoid
|
|
* keeping a strong reference to an application classloader.
|
|
*/
|
|
flushThread.setContextClassLoader(null);
|
|
Runtime.getRuntime().addShutdownHook(flushThread);
|
|
}
|
|
return timer;
|
|
}
|
|
|
|
|
|
// Node manipulation
|
|
boolean addNode(String path)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
markChanged();
|
|
return addNode(path, appName, user, host);
|
|
}
|
|
}
|
|
|
|
void removeNode(String path)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
markChanged();
|
|
removeNode(path, appName, user, host);
|
|
}
|
|
}
|
|
|
|
boolean addChildToNode(String path, String child)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
markChanged();
|
|
return addChildToNode(path, child+"/", appName, user, host);
|
|
}
|
|
}
|
|
|
|
void removeChildFromNode(String path, String child)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
markChanged();
|
|
removeChildFromNode(path, child+"/", appName, user, host);
|
|
}
|
|
}
|
|
|
|
|
|
// Key manipulation
|
|
void addKeyToNode(String path, String key, String value)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
markChanged();
|
|
addKeyToNode(path, key, value, appName, user, host);
|
|
}
|
|
}
|
|
|
|
void removeKeyFromNode(String path, String key)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
markChanged();
|
|
removeKeyFromNode(path, key, appName, user, host);
|
|
}
|
|
}
|
|
|
|
String getKeyFromNode(String path, String key)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
return getKeyFromNode(path, key, appName, user, host);
|
|
}
|
|
}
|
|
|
|
|
|
// Enumerators
|
|
String[] getChildrenForNode(String path)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
return getChildrenForNode(path, appName, user, host);
|
|
}
|
|
}
|
|
|
|
String[] getKeysForNode(String path)
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
return getKeysForNode(path, appName, user, host);
|
|
}
|
|
}
|
|
|
|
|
|
// Synchronization
|
|
boolean synchronize()
|
|
{
|
|
synchronized(MacOSXPreferencesFile.class) {
|
|
return synchronize(appName, user, host);
|
|
}
|
|
}
|
|
|
|
|
|
// CF functions
|
|
// Must be called when synchronized on MacOSXPreferencesFile.class
|
|
private static final native boolean
|
|
addNode(String path, String name, long user, long host);
|
|
private static final native void
|
|
removeNode(String path, String name, long user, long host);
|
|
private static final native boolean
|
|
addChildToNode(String path, String child,
|
|
String name, long user, long host);
|
|
private static final native void
|
|
removeChildFromNode(String path, String child,
|
|
String name, long user, long host);
|
|
private static final native void
|
|
addKeyToNode(String path, String key, String value,
|
|
String name, long user, long host);
|
|
private static final native void
|
|
removeKeyFromNode(String path, String key,
|
|
String name, long user, long host);
|
|
private static final native String
|
|
getKeyFromNode(String path, String key,
|
|
String name, long user, long host);
|
|
private static final native String[]
|
|
getChildrenForNode(String path, String name, long user, long host);
|
|
private static final native String[]
|
|
getKeysForNode(String path, String name, long user, long host);
|
|
private static final native boolean
|
|
synchronize(String name, long user, long host);
|
|
|
|
// CFPreferences host and user values (CFStringRefs)
|
|
private static long cfCurrentUser = currentUser();
|
|
private static long cfAnyUser = anyUser();
|
|
private static long cfCurrentHost = currentHost();
|
|
private static long cfAnyHost = anyHost();
|
|
|
|
// CFPreferences constant accessors
|
|
private static final native long currentUser();
|
|
private static final native long anyUser();
|
|
private static final native long currentHost();
|
|
private static final native long anyHost();
|
|
}
|
|
|