1 /* 2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.prefs; 27 28 import java.util.Objects; 29 30 class MacOSXPreferences extends AbstractPreferences { 31 // fixme need security checks? 32 33 // CF preferences file name for Java nodes with short names 34 // This value is also in MacOSXPreferencesFile.c 35 private static final String defaultAppName = "com.apple.java.util.prefs"; 36 37 // true if this node is a child of userRoot or is userRoot 38 private final boolean isUser; 39 40 // true if this node is userRoot or systemRoot 41 private final boolean isRoot; 42 43 // CF's storage location for this node and its keys 44 private final MacOSXPreferencesFile file; 45 46 // absolutePath() + "/" 47 private final String path; 48 49 // User root and system root nodes 50 private static volatile MacOSXPreferences userRoot; 51 private static volatile MacOSXPreferences systemRoot; 52 53 54 // Returns user root node, creating it if necessary. 55 // Called by MacOSXPreferencesFactory getUserRoot()56 static Preferences getUserRoot() { 57 MacOSXPreferences root = userRoot; 58 if (root == null) { 59 synchronized (MacOSXPreferences.class) { 60 root = userRoot; 61 if (root == null) { 62 userRoot = root = new MacOSXPreferences(true); 63 } 64 } 65 } 66 return root; 67 } 68 69 70 // Returns system root node, creating it if necessary. 71 // Called by MacOSXPreferencesFactory getSystemRoot()72 static Preferences getSystemRoot() { 73 MacOSXPreferences root = systemRoot; 74 if (root == null) { 75 synchronized (MacOSXPreferences.class) { 76 root = systemRoot; 77 if (root == null) { 78 systemRoot = root = new MacOSXPreferences(false); 79 } 80 } 81 } 82 return root; 83 } 84 85 86 // Create a new root node. Called by getUserRoot() and getSystemRoot() 87 // Synchronization is provided by the caller. MacOSXPreferences(boolean newIsUser)88 private MacOSXPreferences(boolean newIsUser) { 89 this(null, "", false, true, newIsUser); 90 } 91 92 93 // Create a new non-root node with the given parent. 94 // Called by childSpi(). MacOSXPreferences(MacOSXPreferences parent, String name)95 private MacOSXPreferences(MacOSXPreferences parent, String name) { 96 this(parent, name, false, false, false); 97 } 98 MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew)99 private MacOSXPreferences(MacOSXPreferences parent, String name, 100 boolean isNew) 101 { 102 this(parent, name, isNew, false, false); 103 } 104 MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew, boolean isRoot, boolean isUser)105 private MacOSXPreferences(MacOSXPreferences parent, String name, 106 boolean isNew, boolean isRoot, boolean isUser) 107 { 108 super(parent, name); 109 this.isRoot = isRoot; 110 if (isRoot) 111 this.isUser = isUser; 112 else 113 this.isUser = isUserNode(); 114 path = isRoot ? absolutePath() : absolutePath() + "/"; 115 file = cfFileForNode(this.isUser); 116 if (isNew) 117 newNode = isNew; 118 else 119 newNode = file.addNode(path); 120 } 121 122 // Create and return the MacOSXPreferencesFile for this node. 123 // Does not write anything to the file. cfFileForNode(boolean isUser)124 private MacOSXPreferencesFile cfFileForNode(boolean isUser) 125 { 126 String name = path; 127 // /one/two/three/four/five/ 128 // The fourth slash is the end of the first three components. 129 // If there is no fourth slash, the name has fewer than 3 components 130 int componentCount = 0; 131 int pos = -1; 132 for (int i = 0; i < 4; i++) { 133 pos = name.indexOf('/', pos+1); 134 if (pos == -1) break; 135 } 136 137 if (pos == -1) { 138 // fewer than three components - use default name 139 name = defaultAppName; 140 } else { 141 // truncate to three components, no leading or trailing '/' 142 // replace '/' with '.' to make filesystem happy 143 // convert to all lowercase to survive on HFS+ 144 name = name.substring(1, pos); 145 name = name.replace('/', '.'); 146 name = name.toLowerCase(); 147 } 148 149 return MacOSXPreferencesFile.getFile(name, isUser); 150 } 151 152 153 // AbstractPreferences implementation 154 @Override putSpi(String key, String value)155 protected void putSpi(String key, String value) 156 { 157 file.addKeyToNode(path, key, value); 158 } 159 160 // AbstractPreferences implementation 161 @Override getSpi(String key)162 protected String getSpi(String key) 163 { 164 return file.getKeyFromNode(path, key); 165 } 166 167 // AbstractPreferences implementation 168 @Override removeSpi(String key)169 protected void removeSpi(String key) 170 { 171 Objects.requireNonNull(key, "Specified key cannot be null"); 172 file.removeKeyFromNode(path, key); 173 } 174 175 176 // AbstractPreferences implementation 177 @Override removeNodeSpi()178 protected void removeNodeSpi() 179 throws BackingStoreException 180 { 181 // Disallow flush or sync between these two operations 182 // (they may be manipulating two different files) 183 synchronized(MacOSXPreferencesFile.class) { 184 ((MacOSXPreferences)parent()).removeChild(name()); 185 file.removeNode(path); 186 } 187 } 188 189 // Erase knowledge about a child of this node. Called by removeNodeSpi. removeChild(String child)190 private void removeChild(String child) 191 { 192 file.removeChildFromNode(path, child); 193 } 194 195 196 // AbstractPreferences implementation 197 @Override childrenNamesSpi()198 protected String[] childrenNamesSpi() 199 throws BackingStoreException 200 { 201 String[] result = file.getChildrenForNode(path); 202 if (result == null) throw new BackingStoreException("Couldn't get list of children for node '" + path + "'"); 203 return result; 204 } 205 206 // AbstractPreferences implementation 207 @Override keysSpi()208 protected String[] keysSpi() 209 throws BackingStoreException 210 { 211 String[] result = file.getKeysForNode(path); 212 if (result == null) throw new BackingStoreException("Couldn't get list of keys for node '" + path + "'"); 213 return result; 214 } 215 216 // AbstractPreferences implementation 217 @Override childSpi(String name)218 protected AbstractPreferences childSpi(String name) 219 { 220 // Add to parent's child list here and disallow sync 221 // because parent and child might be in different files. 222 synchronized(MacOSXPreferencesFile.class) { 223 boolean isNew = file.addChildToNode(path, name); 224 return new MacOSXPreferences(this, name, isNew); 225 } 226 } 227 228 // AbstractPreferences override 229 @Override flush()230 public void flush() 231 throws BackingStoreException 232 { 233 // Flush should *not* check for removal, unlike sync, but should 234 // prevent simultaneous removal. 235 synchronized(lock) { 236 if (isUser) { 237 if (!MacOSXPreferencesFile.flushUser()) { 238 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 239 } 240 } else { 241 if (!MacOSXPreferencesFile.flushWorld()) { 242 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 243 } 244 } 245 } 246 } 247 248 // AbstractPreferences implementation 249 @Override flushSpi()250 protected void flushSpi() 251 throws BackingStoreException 252 { 253 // nothing here - overridden flush() doesn't call this 254 } 255 256 // AbstractPreferences override 257 @Override sync()258 public void sync() 259 throws BackingStoreException 260 { 261 synchronized(lock) { 262 if (isRemoved()) 263 throw new IllegalStateException("Node has been removed"); 264 // fixme! overkill 265 if (isUser) { 266 if (!MacOSXPreferencesFile.syncUser()) { 267 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 268 } 269 } else { 270 if (!MacOSXPreferencesFile.syncWorld()) { 271 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 272 } 273 } 274 } 275 } 276 277 // AbstractPreferences implementation 278 @Override syncSpi()279 protected void syncSpi() 280 throws BackingStoreException 281 { 282 // nothing here - overridden sync() doesn't call this 283 } 284 } 285 286