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