1 /* 2 * Copyright (c) 2011, 2012, 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 // CF's storage location for this node and its keys 41 private final MacOSXPreferencesFile file; 42 43 // absolutePath() + "/" 44 private final String path; 45 46 // User root and system root nodes 47 private static volatile MacOSXPreferences userRoot; 48 private static volatile MacOSXPreferences systemRoot; 49 50 51 // Returns user root node, creating it if necessary. 52 // Called by MacOSXPreferencesFactory getUserRoot()53 static Preferences getUserRoot() { 54 MacOSXPreferences root = userRoot; 55 if (root == null) { 56 synchronized (MacOSXPreferences.class) { 57 root = userRoot; 58 if (root == null) { 59 userRoot = root = new MacOSXPreferences(true); 60 } 61 } 62 } 63 return root; 64 } 65 66 67 // Returns system root node, creating it if necessary. 68 // Called by MacOSXPreferencesFactory getSystemRoot()69 static Preferences getSystemRoot() { 70 MacOSXPreferences root = systemRoot; 71 if (root == null) { 72 synchronized (MacOSXPreferences.class) { 73 root = systemRoot; 74 if (root == null) { 75 systemRoot = root = new MacOSXPreferences(false); 76 } 77 } 78 } 79 return root; 80 } 81 82 83 // Create a new root node. Called by getUserRoot() and getSystemRoot() 84 // Synchronization is provided by the caller. MacOSXPreferences(boolean newIsUser)85 private MacOSXPreferences(boolean newIsUser) { 86 this(null, "", false, true, newIsUser); 87 } 88 89 90 // Create a new non-root node with the given parent. 91 // Called by childSpi(). MacOSXPreferences(MacOSXPreferences parent, String name)92 private MacOSXPreferences(MacOSXPreferences parent, String name) { 93 this(parent, name, false, false, false); 94 } 95 MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew)96 private MacOSXPreferences(MacOSXPreferences parent, String name, 97 boolean isNew) 98 { 99 this(parent, name, isNew, false, false); 100 } 101 MacOSXPreferences(MacOSXPreferences parent, String name, boolean isNew, boolean isRoot, boolean isUser)102 private MacOSXPreferences(MacOSXPreferences parent, String name, 103 boolean isNew, boolean isRoot, boolean isUser) 104 { 105 super(parent, name); 106 if (isRoot) 107 this.isUser = isUser; 108 else 109 this.isUser = isUserNode(); 110 path = isRoot ? absolutePath() : absolutePath() + "/"; 111 file = cfFileForNode(this.isUser); 112 if (isNew) 113 newNode = isNew; 114 else 115 newNode = file.addNode(path); 116 } 117 118 // Create and return the MacOSXPreferencesFile for this node. 119 // Does not write anything to the file. cfFileForNode(boolean isUser)120 private MacOSXPreferencesFile cfFileForNode(boolean isUser) 121 { 122 String name = path; 123 // /one/two/three/four/five/ 124 // The fourth slash is the end of the first three components. 125 // If there is no fourth slash, the name has fewer than 3 components 126 int pos = -1; 127 for (int i = 0; i < 4; i++) { 128 pos = name.indexOf('/', pos+1); 129 if (pos == -1) break; 130 } 131 132 if (pos == -1) { 133 // fewer than three components - use default name 134 name = defaultAppName; 135 } else { 136 // truncate to three components, no leading or trailing '/' 137 // replace '/' with '.' to make filesystem happy 138 // convert to all lowercase to survive on HFS+ 139 name = name.substring(1, pos); 140 name = name.replace('/', '.'); 141 name = name.toLowerCase(); 142 } 143 144 return MacOSXPreferencesFile.getFile(name, isUser); 145 } 146 147 148 // AbstractPreferences implementation 149 @Override putSpi(String key, String value)150 protected void putSpi(String key, String value) 151 { 152 file.addKeyToNode(path, key, value); 153 } 154 155 // AbstractPreferences implementation 156 @Override getSpi(String key)157 protected String getSpi(String key) 158 { 159 return file.getKeyFromNode(path, key); 160 } 161 162 // AbstractPreferences implementation 163 @Override removeSpi(String key)164 protected void removeSpi(String key) 165 { 166 Objects.requireNonNull(key, "Specified key cannot be null"); 167 file.removeKeyFromNode(path, key); 168 } 169 170 171 // AbstractPreferences implementation 172 @Override removeNodeSpi()173 protected void removeNodeSpi() 174 throws BackingStoreException 175 { 176 // Disallow flush or sync between these two operations 177 // (they may be manipulating two different files) 178 synchronized(MacOSXPreferencesFile.class) { 179 ((MacOSXPreferences)parent()).removeChild(name()); 180 file.removeNode(path); 181 } 182 } 183 184 // Erase knowledge about a child of this node. Called by removeNodeSpi. removeChild(String child)185 private void removeChild(String child) 186 { 187 file.removeChildFromNode(path, child); 188 } 189 190 191 // AbstractPreferences implementation 192 @Override childrenNamesSpi()193 protected String[] childrenNamesSpi() 194 throws BackingStoreException 195 { 196 String[] result = file.getChildrenForNode(path); 197 if (result == null) throw new BackingStoreException("Couldn't get list of children for node '" + path + "'"); 198 return result; 199 } 200 201 // AbstractPreferences implementation 202 @Override keysSpi()203 protected String[] keysSpi() 204 throws BackingStoreException 205 { 206 String[] result = file.getKeysForNode(path); 207 if (result == null) throw new BackingStoreException("Couldn't get list of keys for node '" + path + "'"); 208 return result; 209 } 210 211 // AbstractPreferences implementation 212 @Override childSpi(String name)213 protected AbstractPreferences childSpi(String name) 214 { 215 // Add to parent's child list here and disallow sync 216 // because parent and child might be in different files. 217 synchronized(MacOSXPreferencesFile.class) { 218 boolean isNew = file.addChildToNode(path, name); 219 return new MacOSXPreferences(this, name, isNew); 220 } 221 } 222 223 // AbstractPreferences override 224 @Override flush()225 public void flush() 226 throws BackingStoreException 227 { 228 // Flush should *not* check for removal, unlike sync, but should 229 // prevent simultaneous removal. 230 synchronized(lock) { 231 if (isUser) { 232 if (!MacOSXPreferencesFile.flushUser()) { 233 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 234 } 235 } else { 236 if (!MacOSXPreferencesFile.flushWorld()) { 237 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 238 } 239 } 240 } 241 } 242 243 // AbstractPreferences implementation 244 @Override flushSpi()245 protected void flushSpi() 246 throws BackingStoreException 247 { 248 // nothing here - overridden flush() doesn't call this 249 } 250 251 // AbstractPreferences override 252 @Override sync()253 public void sync() 254 throws BackingStoreException 255 { 256 synchronized(lock) { 257 if (isRemoved()) 258 throw new IllegalStateException("Node has been removed"); 259 // fixme! overkill 260 if (isUser) { 261 if (!MacOSXPreferencesFile.syncUser()) { 262 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 263 } 264 } else { 265 if (!MacOSXPreferencesFile.syncWorld()) { 266 throw new BackingStoreException("Synchronization failed for node '" + path + "'"); 267 } 268 } 269 } 270 } 271 272 // AbstractPreferences implementation 273 @Override syncSpi()274 protected void syncSpi() 275 throws BackingStoreException 276 { 277 // nothing here - overridden sync() doesn't call this 278 } 279 } 280 281