* Copyright (c) 2011, 2013, 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("osx");
return null;
}
});
}
private class FlushTask extends TimerTask {
public void run() {
MacOSXPreferencesFile.flushWorld();
}
}
private class SyncTask extends TimerTask {
public void run() {
MacOSXPreferencesFile.syncWorld();
}
}
private static HashMap<String, WeakReference<MacOSXPreferencesFile>>
cachedFiles;
private static HashSet<MacOSXPreferencesFile> changedFiles;
private static Timer timer = null;
private static FlushTask flushTimerTask = null;
private static long flushDelay = -1;
private static long syncInterval = -1;
private String appName;
private long user;
private long host;
String name() { return appName; }
long user() { return user; }
long host() { return host; }
private MacOSXPreferencesFile(String newName, long newUser, long newHost)
{
appName = newName;
user = newUser;
host = newHost;
}
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) {
result = new MacOSXPreferencesFile(newName,
isUser ? cfCurrentUser : cfAnyUser,
isUser ? cfAnyHost : cfCurrentHost);
cachedFiles.put(hashkey, new WeakReference<MacOSXPreferencesFile>(result));
}
initSyncTimerIfNeeded();
return result;
}
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();
}
}
}
if (flushTimerTask != null) {
flushTimerTask.cancel();
flushTimerTask = null;
}
if (changedFiles != null) changedFiles.clear();
return ok;
}
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();
}
}
}
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;
}
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;
}
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;
}
private void markChanged()
{
if (changedFiles == null)
changedFiles = new HashSet<>();
changedFiles.add(this);
if (flushTimerTask == null) {
flushTimerTask = new FlushTask();
timer().schedule(flushTimerTask, flushDelay() * 1000);
}
}
private static synchronized long flushDelay()
{
if (flushDelay == -1) {
try {
flushDelay = Math.max(5, Integer.parseInt(System.getProperty("java.util.prefs.flushDelay", "60")));
} catch (NumberFormatException e) {
flushDelay = 60;
}
}
return flushDelay;
}
private static synchronized void initSyncTimerIfNeeded()
{
if (syncInterval == -1) {
try {
syncInterval = Integer.parseInt(System.getProperty("java.util.prefs.syncInterval", "-2"));
if (syncInterval >= 0) {
syncInterval = Math.max(5, syncInterval);
} else {
syncInterval = -2;
}
} catch (NumberFormatException e) {
syncInterval = -2;
}
if (syncInterval > 0) {
timer().schedule(new TimerTask() {
@Override
public void run() {
MacOSXPreferencesFile.syncWorld();}
}, syncInterval * 1000, syncInterval * 1000);
} else {
}
}
}
private static synchronized Timer timer()
{
if (timer == null) {
timer = new Timer(true);
Thread flushThread = new Thread() {
@Override
public void run() {
flushWorld();
}
};
* keeping a strong reference to an application classloader.
*/
flushThread.setContextClassLoader(null);
Runtime.getRuntime().addShutdownHook(flushThread);
}
return timer;
}
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);
}
}
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);
}
}
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);
}
}
boolean synchronize()
{
synchronized(MacOSXPreferencesFile.class) {
return synchronize(appName, user, host);
}
}
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);
private static long cfCurrentUser = currentUser();
private static long cfAnyUser = anyUser();
private static long cfCurrentHost = currentHost();
private static long cfAnyHost = anyHost();
private static final native long currentUser();
private static final native long anyUser();
private static final native long currentHost();
private static final native long anyHost();
}