src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java
changeset 47216 71c04702a3d5
parent 35311 613162418a3d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.prefs/macosx/classes/java/util/prefs/MacOSXPreferencesFile.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,487 @@
+/*
+ * 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();
+}
+