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