| /* | |
| * Copyright (c) 2011, 2024, 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.Objects; | |
| class MacOSXPreferences extends AbstractPreferences { | |
| // CF preferences file name for Java nodes with short names | |
| // This value is also in MacOSXPreferencesFile.c | |
| private static final String defaultAppName = "com.apple.java.util.prefs"; | |
| // true if this node is a child of userRoot or is userRoot | |
| private final boolean isUser; | |
| // CF's storage location for this node and its keys | |
| private final MacOSXPreferencesFile file; | |
| // absolutePath() + "/" | |
| private final String path; | |
| // User root and system root nodes | |
| private static volatile MacOSXPreferences userRoot; | |
| private static volatile MacOSXPreferences systemRoot; | |
| // Returns user root node, creating it if necessary. | |
| // Called by MacOSXPreferencesFactory | |
| static Preferences getUserRoot() { | |
| MacOSXPreferences root = userRoot; | |
| if (root == null) { | |
| synchronized (MacOSXPreferences.class) { | |
| root = userRoot; | |
| if (root == null) { | |
| userRoot = root = new MacOSXPreferences(true); | |
| } | |
| } | |
| } | |
| return root; | |
| } | |
| // Returns system root node, creating it if necessary. | |
| // Called by MacOSXPreferencesFactory | |
| static Preferences getSystemRoot() { | |
| MacOSXPreferences root = systemRoot; | |
| if (root == null) { | |
| synchronized (MacOSXPreferences.class) { | |
| root = systemRoot; | |
| if (root == null) { | |
| systemRoot = root = new MacOSXPreferences(false); | |
| } | |
| } | |
| } | |
| return root; | |
| } | |
| // Create a new root node. Called by getUserRoot() and getSystemRoot() | |
| // Synchronization is provided by the caller. | |
| private MacOSXPreferences(boolean newIsUser) { | |
| this(null, "", false, true, newIsUser); | |
| } | |
| // Create a new non-root node with the given parent. | |
| // Called by childSpi(). | |
| private MacOSXPreferences(MacOSXPreferences parent, String name) { | |
| this(parent, name, false, false, false); | |
| } | |
| private MacOSXPreferences(MacOSXPreferences parent, String name, | |
| boolean isNew) | |
| { | |
| this(parent, name, isNew, false, false); | |
| } | |
| private MacOSXPreferences(MacOSXPreferences parent, String name, | |
| boolean isNew, boolean isRoot, boolean isUser) | |
| { | |
| super(parent, name); | |
| if (isRoot) | |
| this.isUser = isUser; | |
| else | |
| this.isUser = isUserNode(); | |
| path = isRoot ? absolutePath() : absolutePath() + "/"; | |
| file = cfFileForNode(this.isUser); | |
| if (isNew) | |
| newNode = isNew; | |
| else | |
| newNode = file.addNode(path); | |
| } | |
| // Create and return the MacOSXPreferencesFile for this node. | |
| // Does not write anything to the file. | |
| private MacOSXPreferencesFile cfFileForNode(boolean isUser) | |
| { | |
| String name = path; | |
| // /one/two/three/four/five/ | |
| // The fourth slash is the end of the first three components. | |
| // If there is no fourth slash, the name has fewer than 3 components | |
| int pos = -1; | |
| for (int i = 0; i < 4; i++) { | |
| pos = name.indexOf('/', pos+1); | |
| if (pos == -1) break; | |
| } | |
| if (pos == -1) { | |
| // fewer than three components - use default name | |
| name = defaultAppName; | |
| } else { | |
| // truncate to three components, no leading or trailing '/' | |
| // replace '/' with '.' to make filesystem happy | |
| // convert to all lowercase to survive on HFS+ | |
| name = name.substring(1, pos); | |
| name = name.replace('/', '.'); | |
| name = name.toLowerCase(); | |
| } | |
| return MacOSXPreferencesFile.getFile(name, isUser); | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected void putSpi(String key, String value) | |
| { | |
| file.addKeyToNode(path, key, value); | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected String getSpi(String key) | |
| { | |
| return file.getKeyFromNode(path, key); | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected void removeSpi(String key) | |
| { | |
| Objects.requireNonNull(key, "Specified key cannot be null"); | |
| file.removeKeyFromNode(path, key); | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected void removeNodeSpi() | |
| throws BackingStoreException | |
| { | |
| // Disallow flush or sync between these two operations | |
| // (they may be manipulating two different files) | |
| synchronized(MacOSXPreferencesFile.class) { | |
| ((MacOSXPreferences)parent()).removeChild(name()); | |
| file.removeNode(path); | |
| } | |
| } | |
| // Erase knowledge about a child of this node. Called by removeNodeSpi. | |
| private void removeChild(String child) | |
| { | |
| file.removeChildFromNode(path, child); | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected String[] childrenNamesSpi() | |
| throws BackingStoreException | |
| { | |
| String[] result = file.getChildrenForNode(path); | |
| if (result == null) throw new BackingStoreException("Couldn't get list of children for node '" + path + "'"); | |
| return result; | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected String[] keysSpi() | |
| throws BackingStoreException | |
| { | |
| String[] result = file.getKeysForNode(path); | |
| if (result == null) throw new BackingStoreException("Couldn't get list of keys for node '" + path + "'"); | |
| return result; | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected AbstractPreferences childSpi(String name) | |
| { | |
| // Add to parent's child list here and disallow sync | |
| // because parent and child might be in different files. | |
| synchronized(MacOSXPreferencesFile.class) { | |
| boolean isNew = file.addChildToNode(path, name); | |
| return new MacOSXPreferences(this, name, isNew); | |
| } | |
| } | |
| // AbstractPreferences override | |
| @Override | |
| public void flush() | |
| throws BackingStoreException | |
| { | |
| // Flush should *not* check for removal, unlike sync, but should | |
| // prevent simultaneous removal. | |
| synchronized(lock) { | |
| if (isUser) { | |
| if (!MacOSXPreferencesFile.flushUser()) { | |
| throw new BackingStoreException("Synchronization failed for node '" + path + "'"); | |
| } | |
| } else { | |
| if (!MacOSXPreferencesFile.flushWorld()) { | |
| throw new BackingStoreException("Synchronization failed for node '" + path + "'"); | |
| } | |
| } | |
| } | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected void flushSpi() | |
| throws BackingStoreException | |
| { | |
| // nothing here - overridden flush() doesn't call this | |
| } | |
| // AbstractPreferences override | |
| @Override | |
| public void sync() | |
| throws BackingStoreException | |
| { | |
| synchronized(lock) { | |
| if (isRemoved()) | |
| throw new IllegalStateException("Node has been removed"); | |
| // fixme! overkill | |
| if (isUser) { | |
| if (!MacOSXPreferencesFile.syncUser()) { | |
| throw new BackingStoreException("Synchronization failed for node '" + path + "'"); | |
| } | |
| } else { | |
| if (!MacOSXPreferencesFile.syncWorld()) { | |
| throw new BackingStoreException("Synchronization failed for node '" + path + "'"); | |
| } | |
| } | |
| } | |
| } | |
| // AbstractPreferences implementation | |
| @Override | |
| protected void syncSpi() | |
| throws BackingStoreException | |
| { | |
| // nothing here - overridden sync() doesn't call this | |
| } | |
| } | |