Path: blob/master/src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java
41159 views
/*1* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util.prefs;2627import java.util.HashMap;28import java.util.HashSet;29import java.util.Iterator;30import java.util.Timer;31import java.util.TimerTask;32import java.lang.ref.WeakReference;333435/*36MacOSXPreferencesFile synchronization:3738Everything is synchronized on MacOSXPreferencesFile.class. This prevents:39* simultaneous updates to cachedFiles or changedFiles40* simultaneous creation of two objects for the same name+user+host triplet41* simultaneous modifications to the same file42* modifications during syncWorld/flushWorld43* (in MacOSXPreferences.removeNodeSpi()) modification or sync during44multi-step node removal process45... among other things.46*/47/*48Timers. There are two timers that control synchronization of prefs data to49and from disk.5051* Sync timer periodically calls syncWorld() to force external disk changes52(e.g. from another VM) into the memory cache. The sync timer runs even53if there are no outstanding local changes. The sync timer syncs all live54MacOSXPreferencesFile objects (the cachedFiles list).55The sync timer period is controlled by the java.util.prefs.syncInterval56property (same as FileSystemPreferences). By default there is *no*57sync timer (unlike FileSystemPreferences); it is only enabled if the58syncInterval property is set. The minimum interval is 5 seconds.5960* Flush timer calls flushWorld() to force local changes to disk.61The flush timer is scheduled to fire some time after each pref change,62unless it's already scheduled to fire before that. syncWorld and63flushWorld will cancel any outstanding flush timer as unnecessary.64The flush timer flushes all changed files (the changedFiles list).65The time between pref write and flush timer call is controlled by the66java.util.prefs.flushDelay property (unlike FileSystemPreferences).67The default is 60 seconds and the minimum is 5 seconds.6869The flush timer's behavior is required by the Java Preferences spec70("changes will eventually propagate to the persistent backing store with71an implementation-dependent delay"). The sync timer is not required by72the spec (multiple VMs are only required to not corrupt the prefs), but73the periodic sync is implemented by FileSystemPreferences and may be74useful to some programs. The sync timer is disabled by default because75it's expensive and is usually not necessary.76*/7778@SuppressWarnings("removal")79class MacOSXPreferencesFile {8081static {82java.security.AccessController.doPrivileged(83new java.security.PrivilegedAction<Void>() {84public Void run() {85System.loadLibrary("prefs");86return null;87}88});89}9091private class FlushTask extends TimerTask {92public void run() {93MacOSXPreferencesFile.flushWorld();94}95}9697private class SyncTask extends TimerTask {98public void run() {99MacOSXPreferencesFile.syncWorld();100}101}102103// Maps string -> weak reference to MacOSXPreferencesFile104private static HashMap<String, WeakReference<MacOSXPreferencesFile>>105cachedFiles;106// Files that may have unflushed changes107private static HashSet<MacOSXPreferencesFile> changedFiles;108109110// Timer and pending sync and flush tasks (which are both scheduled111// on the same timer)112private static Timer timer = null;113private static FlushTask flushTimerTask = null;114private static long flushDelay = -1; // in seconds (min 5, default 60)115private static long syncInterval = -1; // (min 5, default negative == off)116117private String appName;118private long user;119private long host;120121String name() { return appName; }122long user() { return user; }123long host() { return host; }124125// private constructor - use factory method getFile() instead126private MacOSXPreferencesFile(String newName, long newUser, long newHost)127{128appName = newName;129user = newUser;130host = newHost;131}132133// Factory method134// Always returns the same object for the given name+user+host135static synchronized MacOSXPreferencesFile136getFile(String newName, boolean isUser)137{138MacOSXPreferencesFile result = null;139140if (cachedFiles == null)141cachedFiles = new HashMap<>();142143String hashkey =144newName + String.valueOf(isUser);145WeakReference<MacOSXPreferencesFile> hashvalue = cachedFiles.get(hashkey);146if (hashvalue != null) {147result = hashvalue.get();148}149if (result == null) {150// Java user node == CF current user, any host151// Java system node == CF any user, current host152result = new MacOSXPreferencesFile(newName,153isUser ? cfCurrentUser : cfAnyUser,154isUser ? cfAnyHost : cfCurrentHost);155cachedFiles.put(hashkey, new WeakReference<MacOSXPreferencesFile>(result));156}157158// Don't schedule this file for flushing until some nodes or159// keys are added to it.160161// Do set up the sync timer if requested; sync timer affects reads162// as well as writes.163initSyncTimerIfNeeded();164165return result;166}167168169// Write all prefs changes to disk and clear all cached prefs values170// (so the next read will read from disk).171static synchronized boolean syncWorld()172{173boolean ok = true;174175if (cachedFiles != null && !cachedFiles.isEmpty()) {176Iterator<WeakReference<MacOSXPreferencesFile>> iter =177cachedFiles.values().iterator();178while (iter.hasNext()) {179WeakReference<MacOSXPreferencesFile> ref = iter.next();180MacOSXPreferencesFile f = ref.get();181if (f != null) {182if (!f.synchronize()) ok = false;183} else {184iter.remove();185}186}187}188189// Kill any pending flush190if (flushTimerTask != null) {191flushTimerTask.cancel();192flushTimerTask = null;193}194195// Clear changed file list. The changed files were guaranteed to196// have been in the cached file list (because there was a strong197// reference from changedFiles.198if (changedFiles != null) changedFiles.clear();199200return ok;201}202203204// Sync only current user preferences205static synchronized boolean syncUser() {206boolean ok = true;207if (cachedFiles != null && !cachedFiles.isEmpty()) {208Iterator<WeakReference<MacOSXPreferencesFile>> iter =209cachedFiles.values().iterator();210while (iter.hasNext()) {211WeakReference<MacOSXPreferencesFile> ref = iter.next();212MacOSXPreferencesFile f = ref.get();213if (f != null && f.user == cfCurrentUser) {214if (!f.synchronize()) {215ok = false;216}217} else {218iter.remove();219}220}221}222// Remove synchronized file from changed file list. The changed files were223// guaranteed to have been in the cached file list (because there was a strong224// reference from changedFiles.225if (changedFiles != null) {226Iterator<MacOSXPreferencesFile> iterChanged = changedFiles.iterator();227while (iterChanged.hasNext()) {228MacOSXPreferencesFile f = iterChanged.next();229if (f != null && f.user == cfCurrentUser)230iterChanged.remove();231}232}233return ok;234}235236//Flush only current user preferences237static synchronized boolean flushUser() {238boolean ok = true;239if (changedFiles != null && !changedFiles.isEmpty()) {240Iterator<MacOSXPreferencesFile> iterator = changedFiles.iterator();241while(iterator.hasNext()) {242MacOSXPreferencesFile f = iterator.next();243if (f.user == cfCurrentUser) {244if (!f.synchronize())245ok = false;246else247iterator.remove();248}249}250}251return ok;252}253254// Write all prefs changes to disk, but do not clear all cached prefs255// values. Also kills any scheduled flush task.256// There's no CFPreferencesFlush() (<rdar://problem/3049129>), so lots of cached prefs257// are cleared anyway.258static synchronized boolean flushWorld()259{260boolean ok = true;261262if (changedFiles != null && !changedFiles.isEmpty()) {263for (MacOSXPreferencesFile f : changedFiles) {264if (!f.synchronize())265ok = false;266}267changedFiles.clear();268}269270if (flushTimerTask != null) {271flushTimerTask.cancel();272flushTimerTask = null;273}274275return ok;276}277278// Mark this prefs file as changed. The changes will be flushed in279// at most flushDelay() seconds.280// Must be called when synchronized on MacOSXPreferencesFile.class281private void markChanged()282{283// Add this file to the changed file list284if (changedFiles == null)285changedFiles = new HashSet<>();286changedFiles.add(this);287288// Schedule a new flush and a shutdown hook, if necessary289if (flushTimerTask == null) {290flushTimerTask = new FlushTask();291timer().schedule(flushTimerTask, flushDelay() * 1000);292}293}294295// Return the flush delay, initializing from a property if necessary.296private static synchronized long flushDelay()297{298if (flushDelay == -1) {299try {300// flush delay >= 5, default 60301flushDelay = Math.max(5, Integer.parseInt(System.getProperty("java.util.prefs.flushDelay", "60")));302} catch (NumberFormatException e) {303flushDelay = 60;304}305}306return flushDelay;307}308309// Initialize and run the sync timer, if the sync timer property is set310// and the sync timer hasn't already been started.311private static synchronized void initSyncTimerIfNeeded()312{313// syncInterval: -1 is uninitialized, other negative is off,314// positive is seconds between syncs (min 5).315316if (syncInterval == -1) {317try {318syncInterval = Integer.parseInt(System.getProperty("java.util.prefs.syncInterval", "-2"));319if (syncInterval >= 0) {320// minimum of 5 seconds321syncInterval = Math.max(5, syncInterval);322} else {323syncInterval = -2; // default off324}325} catch (NumberFormatException e) {326syncInterval = -2; // bad property value - default off327}328329if (syncInterval > 0) {330timer().schedule(new TimerTask() {331@Override332public void run() {333MacOSXPreferencesFile.syncWorld();}334}, syncInterval * 1000, syncInterval * 1000);335} else {336// syncInterval property not set. No sync timer ever.337}338}339}340341// Return the timer used for flush and sync, creating it if necessary.342private static synchronized Timer timer()343{344if (timer == null) {345timer = new Timer(true); // daemon346Thread flushThread =347new Thread(null, null, "Flush Thread", 0, false) {348@Override349public void run() {350flushWorld();351}352};353/* Set context class loader to null in order to avoid354* keeping a strong reference to an application classloader.355*/356flushThread.setContextClassLoader(null);357Runtime.getRuntime().addShutdownHook(flushThread);358}359return timer;360}361362363// Node manipulation364boolean addNode(String path)365{366synchronized(MacOSXPreferencesFile.class) {367markChanged();368return addNode(path, appName, user, host);369}370}371372void removeNode(String path)373{374synchronized(MacOSXPreferencesFile.class) {375markChanged();376removeNode(path, appName, user, host);377}378}379380boolean addChildToNode(String path, String child)381{382synchronized(MacOSXPreferencesFile.class) {383markChanged();384return addChildToNode(path, child+"/", appName, user, host);385}386}387388void removeChildFromNode(String path, String child)389{390synchronized(MacOSXPreferencesFile.class) {391markChanged();392removeChildFromNode(path, child+"/", appName, user, host);393}394}395396397// Key manipulation398void addKeyToNode(String path, String key, String value)399{400synchronized(MacOSXPreferencesFile.class) {401markChanged();402addKeyToNode(path, key, value, appName, user, host);403}404}405406void removeKeyFromNode(String path, String key)407{408synchronized(MacOSXPreferencesFile.class) {409markChanged();410removeKeyFromNode(path, key, appName, user, host);411}412}413414String getKeyFromNode(String path, String key)415{416synchronized(MacOSXPreferencesFile.class) {417return getKeyFromNode(path, key, appName, user, host);418}419}420421422// Enumerators423String[] getChildrenForNode(String path)424{425synchronized(MacOSXPreferencesFile.class) {426return getChildrenForNode(path, appName, user, host);427}428}429430String[] getKeysForNode(String path)431{432synchronized(MacOSXPreferencesFile.class) {433return getKeysForNode(path, appName, user, host);434}435}436437438// Synchronization439boolean synchronize()440{441synchronized(MacOSXPreferencesFile.class) {442return synchronize(appName, user, host);443}444}445446447// CF functions448// Must be called when synchronized on MacOSXPreferencesFile.class449private static final native boolean450addNode(String path, String name, long user, long host);451private static final native void452removeNode(String path, String name, long user, long host);453private static final native boolean454addChildToNode(String path, String child,455String name, long user, long host);456private static final native void457removeChildFromNode(String path, String child,458String name, long user, long host);459private static final native void460addKeyToNode(String path, String key, String value,461String name, long user, long host);462private static final native void463removeKeyFromNode(String path, String key,464String name, long user, long host);465private static final native String466getKeyFromNode(String path, String key,467String name, long user, long host);468private static final native String[]469getChildrenForNode(String path, String name, long user, long host);470private static final native String[]471getKeysForNode(String path, String name, long user, long host);472private static final native boolean473synchronize(String name, long user, long host);474475// CFPreferences host and user values (CFStringRefs)476private static long cfCurrentUser = currentUser();477private static long cfAnyUser = anyUser();478private static long cfCurrentHost = currentHost();479private static long cfAnyHost = anyHost();480481// CFPreferences constant accessors482private static final native long currentUser();483private static final native long anyUser();484private static final native long currentHost();485private static final native long anyHost();486}487488489490