0N/A/*
2362N/A * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage java.util.prefs;
0N/A
0N/Aimport java.util.HashMap;
0N/Aimport java.util.HashSet;
0N/Aimport java.util.Iterator;
0N/Aimport java.util.Timer;
0N/Aimport java.util.TimerTask;
0N/Aimport java.lang.ref.WeakReference;
0N/A
0N/A
0N/A/*
0N/A MacOSXPreferencesFile synchronization:
0N/A
0N/A Everything is synchronized on MacOSXPreferencesFile.class. This prevents:
0N/A * simultaneous updates to cachedFiles or changedFiles
0N/A * simultaneous creation of two objects for the same name+user+host triplet
0N/A * simultaneous modifications to the same file
0N/A * modifications during syncWorld/flushWorld
0N/A * (in MacOSXPreferences.removeNodeSpi()) modification or sync during
0N/A multi-step node removal process
0N/A ... among other things.
0N/A*/
0N/A/*
0N/A Timers. There are two timers that control synchronization of prefs data to
0N/A and from disk.
0N/A
0N/A * Sync timer periodically calls syncWorld() to force external disk changes
0N/A (e.g. from another VM) into the memory cache. The sync timer runs even
0N/A if there are no outstanding local changes. The sync timer syncs all live
0N/A MacOSXPreferencesFile objects (the cachedFiles list).
0N/A The sync timer period is controlled by the java.util.prefs.syncInterval
0N/A property (same as FileSystemPreferences). By default there is *no*
0N/A sync timer (unlike FileSystemPreferences); it is only enabled if the
0N/A syncInterval property is set. The minimum interval is 5 seconds.
0N/A
0N/A * Flush timer calls flushWorld() to force local changes to disk.
0N/A The flush timer is scheduled to fire some time after each pref change,
0N/A unless it's already scheduled to fire before that. syncWorld and
0N/A flushWorld will cancel any outstanding flush timer as unnecessary.
0N/A The flush timer flushes all changed files (the changedFiles list).
0N/A The time between pref write and flush timer call is controlled by the
0N/A java.util.prefs.flushDelay property (unlike FileSystemPreferences).
0N/A The default is 60 seconds and the minimum is 5 seconds.
0N/A
0N/A The flush timer's behavior is required by the Java Preferences spec
0N/A ("changes will eventually propagate to the persistent backing store with
0N/A an implementation-dependent delay"). The sync timer is not required by
0N/A the spec (multiple VMs are only required to not corrupt the prefs), but
0N/A the periodic sync is implemented by FileSystemPreferences and may be
0N/A useful to some programs. The sync timer is disabled by default because
0N/A it's expensive and is usually not necessary.
0N/A*/
0N/A
0N/Aclass MacOSXPreferencesFile {
0N/A
0N/A static {
0N/A java.security.AccessController.doPrivileged(new sun.security.action.LoadLibraryAction("osx"));
0N/A }
0N/A
0N/A private class FlushTask extends TimerTask {
0N/A public void run() {
0N/A MacOSXPreferencesFile.flushWorld();
0N/A }
0N/A }
0N/A
0N/A private class SyncTask extends TimerTask {
0N/A public void run() {
0N/A MacOSXPreferencesFile.syncWorld();
0N/A }
0N/A }
0N/A
0N/A // Maps string -> weak reference to MacOSXPreferencesFile
0N/A private static HashMap cachedFiles = null;
0N/A // Files that may have unflushed changes
0N/A private static HashSet changedFiles = null;
0N/A
0N/A
0N/A // Timer and pending sync and flush tasks (which are both scheduled
0N/A // on the same timer)
0N/A private static Timer timer = null;
0N/A private static FlushTask flushTimerTask = null;
0N/A private static long flushDelay = -1; // in seconds (min 5, default 60)
0N/A private static long syncInterval = -1; // (min 5, default negative == off)
0N/A
0N/A private String appName;
0N/A private long user;
0N/A private long host;
0N/A
0N/A String name() { return appName; }
0N/A long user() { return user; }
0N/A long host() { return host; }
0N/A
0N/A // private contructor - use factory method getFile() instead
0N/A private MacOSXPreferencesFile(String newName, long newUser, long newHost)
0N/A {
0N/A appName = newName;
0N/A user = newUser;
0N/A host = newHost;
0N/A }
0N/A
0N/A // Factory method
0N/A // Always returns the same object for the given name+user+host
0N/A static synchronized MacOSXPreferencesFile
0N/A getFile(String newName, boolean isUser)
0N/A {
0N/A MacOSXPreferencesFile result = null;
0N/A
0N/A if (cachedFiles == null) cachedFiles = new HashMap();
0N/A
0N/A String hashkey =
0N/A newName + String.valueOf(isUser);
0N/A WeakReference hashvalue = (WeakReference)cachedFiles.get(hashkey);
0N/A if (hashvalue != null) {
0N/A result = (MacOSXPreferencesFile)hashvalue.get();
0N/A }
0N/A if (result == null) {
0N/A // Java user node == CF current user, any host
0N/A // Java system node == CF any user, current host
0N/A result = new MacOSXPreferencesFile(newName,
0N/A isUser ? cfCurrentUser : cfAnyUser,
0N/A isUser ? cfAnyHost : cfCurrentHost);
0N/A cachedFiles.put(hashkey, new WeakReference(result));
0N/A }
0N/A
0N/A // Don't schedule this file for flushing until some nodes or
0N/A // keys are added to it.
0N/A
0N/A // Do set up the sync timer if requested; sync timer affects reads
0N/A // as well as writes.
0N/A initSyncTimerIfNeeded();
0N/A
0N/A return result;
0N/A }
0N/A
0N/A
0N/A // Write all prefs changes to disk and clear all cached prefs values
0N/A // (so the next read will read from disk).
0N/A static synchronized boolean syncWorld()
0N/A {
0N/A boolean ok = true;
0N/A
0N/A if (cachedFiles != null && !cachedFiles.isEmpty()) {
0N/A Iterator iter = cachedFiles.values().iterator();
0N/A while (iter.hasNext()) {
0N/A WeakReference ref = (WeakReference)iter.next();
0N/A MacOSXPreferencesFile f = (MacOSXPreferencesFile)ref.get();
0N/A if (f != null) {
0N/A if (!f.synchronize()) ok = false;
0N/A } else {
0N/A iter.remove();
0N/A }
0N/A }
0N/A }
0N/A
0N/A // Kill any pending flush
0N/A if (flushTimerTask != null) {
0N/A flushTimerTask.cancel();
0N/A flushTimerTask = null;
0N/A }
0N/A
0N/A // Clear changed file list. The changed files were guaranteed to
0N/A // have been in the cached file list (because there was a strong
0N/A // reference from changedFiles.
0N/A if (changedFiles != null) changedFiles.clear();
0N/A
0N/A return ok;
0N/A }
0N/A
0N/A
0N/A // Sync only current user preferences
0N/A static synchronized boolean syncUser() {
0N/A boolean ok = true;
0N/A if (cachedFiles != null && !cachedFiles.isEmpty()) {
0N/A Iterator<WeakReference> iter = cachedFiles.values().iterator();
0N/A while (iter.hasNext()) {
0N/A WeakReference ref = iter.next();
0N/A MacOSXPreferencesFile f = (MacOSXPreferencesFile)ref.get();
0N/A if (f != null && f.user == cfCurrentUser) {
0N/A if (!f.synchronize()) {
0N/A ok = false;
0N/A }
0N/A } else {
0N/A iter.remove();
0N/A }
0N/A }
0N/A }
0N/A // Remove synchronized file from changed file list. The changed files were
0N/A // guaranteed to have been in the cached file list (because there was a strong
0N/A // reference from changedFiles.
0N/A if (changedFiles != null) {
0N/A Iterator<MacOSXPreferencesFile> iterChanged = changedFiles.iterator();
0N/A while (iterChanged.hasNext()) {
0N/A MacOSXPreferencesFile f = iterChanged.next();
0N/A if (f != null && f.user == cfCurrentUser)
0N/A iterChanged.remove();
0N/A }
0N/A }
0N/A return ok;
0N/A }
0N/A
0N/A //Flush only current user preferences
0N/A static synchronized boolean flushUser() {
0N/A boolean ok = true;
0N/A if (changedFiles != null && !changedFiles.isEmpty()) {
0N/A Iterator<MacOSXPreferencesFile> iterator = changedFiles.iterator();
0N/A while(iterator.hasNext()) {
0N/A MacOSXPreferencesFile f = iterator.next();
0N/A if (f.user == cfCurrentUser) {
0N/A if (!f.synchronize())
0N/A ok = false;
0N/A else
0N/A iterator.remove();
0N/A }
0N/A }
0N/A }
0N/A return ok;
0N/A }
0N/A
0N/A // Write all prefs changes to disk, but do not clear all cached prefs
0N/A // values. Also kills any scheduled flush task.
0N/A // There's no CFPreferencesFlush() (<rdar://problem/3049129>), so lots of cached prefs
0N/A // are cleared anyway.
0N/A static synchronized boolean flushWorld()
0N/A {
0N/A boolean ok = true;
0N/A
0N/A if (changedFiles != null && !changedFiles.isEmpty()) {
0N/A Iterator iter = changedFiles.iterator();
0N/A while (iter.hasNext()) {
0N/A MacOSXPreferencesFile f = (MacOSXPreferencesFile)iter.next();
0N/A if (!f.synchronize()) ok = false;
0N/A }
0N/A
0N/A changedFiles.clear();
0N/A }
0N/A
0N/A if (flushTimerTask != null) {
0N/A flushTimerTask.cancel();
0N/A flushTimerTask = null;
0N/A }
0N/A
0N/A return ok;
0N/A }
0N/A
0N/A // Mark this prefs file as changed. The changes will be flushed in
0N/A // at most flushDelay() seconds.
0N/A // Must be called when synchronized on MacOSXPreferencesFile.class
0N/A private void markChanged()
0N/A {
0N/A // Add this file to the changed file list
0N/A if (changedFiles == null) changedFiles = new HashSet();
0N/A changedFiles.add(this);
0N/A
// 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() {
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() {
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();
}