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