Frames | No Frames |
1: /* GConfBasedPreferences.java -- GConf based Preferences implementation 2: Copyright (C) 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package gnu.java.util.prefs; 39: 40: import gnu.java.util.prefs.gconf.GConfNativePeer; 41: 42: import java.security.Permission; 43: 44: import java.util.List; 45: import java.util.prefs.AbstractPreferences; 46: import java.util.prefs.BackingStoreException; 47: 48: /** 49: * This is a GConf based preference implementation which writes the preferences 50: * as GConf key-value pairs. System Root is defined to be the 51: * <code>"/system"</code> directory of GConf for the current user, while User 52: * Root is <code>"/apps/java"</code>. These defaults can be modified by 53: * defining two system properties:<br /> 54: * <br /> 55: * User Root:<br /> 56: * <br /> 57: * 58: * <pre> 59: * gnu.java.util.prefs.gconf.user_root 60: * </pre> 61: * 62: * <br /> 63: * <br /> 64: * and System Root:<br /> 65: * <br /> 66: * 67: * <pre> 68: * gnu.java.util.prefs.gconf.system_root 69: * </pre> 70: * 71: * <br /> 72: * 73: * @author Mario Torre <neugens@limasoftware.net> 74: */ 75: public class GConfBasedPreferences 76: extends AbstractPreferences 77: { 78: /** Get access to Runtime permission */ 79: private static final Permission PERMISSION 80: = new RuntimePermission("preferences"); 81: 82: /** CGonf client backend */ 83: private static GConfNativePeer backend = new GConfNativePeer(); 84: 85: /** Default user root path */ 86: private static final String DEFAULT_USER_ROOT = "/apps/classpath"; 87: 88: /** Default system root path */ 89: private static final String DEFAULT_SYSTEM_ROOT = "/system"; 90: 91: /** current node full path */ 92: private String node = ""; 93: 94: /** True if this is a preference node in the user tree, false otherwise. */ 95: private final boolean isUser; 96: 97: /** 98: * Creates a preference root user node. 99: */ 100: public GConfBasedPreferences() 101: { 102: this(true); 103: } 104: 105: /** 106: * Creates a preference root node. When <code>isUser</code> is true it will 107: * be user node otherwise it will be a system node. 108: */ 109: public GConfBasedPreferences(boolean isUser) 110: { 111: this(null, "", isUser); 112: } 113: 114: /** 115: * Creates a new preference node given a parent node and a name, which has to 116: * be relative to its parent. When <code>isUser</code> is true it will be user 117: * node otherwise it will be a system node. 118: * 119: * @param parent The parent node of this newly created node. 120: * @param name A name relative to the parent node. 121: * @param isUser Set to <code>true</code> initializes this node to be 122: * a user node, <code>false</code> initialize it to be a system node. 123: */ 124: public GConfBasedPreferences(AbstractPreferences parent, String name, 125: boolean isUser) 126: { 127: super(parent, name); 128: this.isUser = isUser; 129: 130: // stores the fully qualified name of this node 131: String absolutePath = this.absolutePath(); 132: if (absolutePath != null && absolutePath.endsWith("/")) 133: { 134: absolutePath = absolutePath.substring(0, absolutePath.length() - 1); 135: } 136: 137: // strip invalid characters 138: // please, note that all names are unescaped into the native peer 139: int index = absolutePath.lastIndexOf('/'); 140: if (index > -1) 141: { 142: absolutePath = absolutePath.substring(0, index + 1); 143: absolutePath = absolutePath + GConfNativePeer.escapeString(name); 144: } 145: 146: this.node = this.getRealRoot(isUser) + absolutePath; 147: 148: boolean nodeExist = backend.nodeExist(this.node); 149: 150: this.newNode = !nodeExist; 151: } 152: 153: /** 154: * Returns a child node with the given name. 155: * If the child node does not exists, it will be created. 156: * 157: * @param name The name of the requested node. 158: * @return A new reference to the node, creating the node if it is necessary. 159: */ 160: protected AbstractPreferences childSpi(String name) 161: { 162: // we don't check anything here, if the node is a new node this will be 163: // detected in the constructor, so we simply return a new reference to 164: // the requested node. 165: 166: GConfBasedPreferences preferenceNode 167: = new GConfBasedPreferences(this, name, this.isUser); 168: 169: // register the node for to GConf so that it can listen 170: // events outside the scope of the application 171: backend.startWatchingNode(this.node); 172: 173: return preferenceNode; 174: } 175: 176: /** 177: * Returns an array of names of the children of this preference node. 178: * If the current node does not have children, the returned array will be 179: * of <code>size</code> 0 (that is, not <code>null</code>). 180: * 181: * @return A <code>String</code> array of names of children of the current 182: * node. 183: * @throws BackingStoreException if this operation cannot be completed. 184: */ 185: protected String[] childrenNamesSpi() throws BackingStoreException 186: { 187: List<String> nodeList = backend.getChildrenNodes(this.node); 188: String[] nodes = new String[nodeList.size()]; 189: nodeList.toArray(nodes); 190: 191: return nodes; 192: } 193: 194: /** 195: * Suggest a flush to the backend. Actually, this is only a suggestion as 196: * GConf handles this for us asynchronously. More over, both sync and flush 197: * have the same meaning in this class, so calling sync has exactly the same 198: * effect. 199: * 200: * @see #sync 201: * @throws BackingStoreException if this operation cannot be completed. 202: */ 203: public void flush() throws BackingStoreException 204: { 205: backend.suggestSync(); 206: } 207: 208: /** 209: * Request a flush. 210: * 211: * @see #flush 212: * @throws BackingStoreException if this operation cannot be completed. 213: */ 214: protected void flushSpi() throws BackingStoreException 215: { 216: this.flush(); 217: } 218: 219: /** 220: * Returns all of the key in this preference node. 221: * If the current node does not have preferences, the returned array will be 222: * of size zero. 223: * 224: * @return A <code>String</code> array of keys stored under the current 225: * node. 226: * @throws BackingStoreException if this operation cannot be completed. 227: */ 228: protected String[] keysSpi() throws BackingStoreException 229: { 230: List<String> keyList = backend.getKeys(this.node); 231: String[] keys = new String[keyList.size()]; 232: keyList.toArray(keys); 233: 234: return keys; 235: } 236: 237: /** 238: * Does a recursive postorder traversal of the preference tree, starting from 239: * the given directory invalidating every preference found in the node. 240: * 241: * @param directory The name of the starting directory (node) 242: */ 243: private void postorderRemove(String directory) 244: { 245: try 246: { 247: // gets the listing of directories in this node 248: List<String> dirs = backend.getChildrenNodes(directory); 249: 250: if (dirs.size() != 0) 251: { 252: for (String currentDir : dirs) 253: { 254: // recursive search inside this directory 255: postorderRemove(currentDir); 256: } 257: } 258: 259: // remove all the keys associated to this directory 260: List<String> entries = backend.getKeys(directory); 261: 262: if (entries.size() != 0) 263: { 264: for (String key : entries) 265: { 266: this.removeSpi(key); 267: } 268: } 269: } 270: catch (BackingStoreException ex) 271: { 272: /* ignore */ 273: } 274: } 275: 276: /** 277: * Stores the given key-value pair into this preference node. 278: * 279: * @param key The key of this preference. 280: * @param value The value of this preference. 281: */ 282: protected void putSpi(String key, String value) 283: { 284: backend.setString(this.getGConfKey(key), value); 285: } 286: 287: /** 288: * Removes this preference node, including all its children. 289: * Also removes the preferences associated. 290: */ 291: protected void removeNodeSpi() throws BackingStoreException 292: { 293: this.postorderRemove(this.node); 294: this.flush(); 295: } 296: 297: /** 298: * Removes the given key from this preference node. 299: * If the key does not exist, no operation is performed. 300: * 301: * @param key The key to remove. 302: */ 303: protected void removeSpi(String key) 304: { 305: backend.unset(this.getGConfKey(key)); 306: } 307: 308: /** 309: * Suggest a sync to the backend. Actually, this is only a suggestion as GConf 310: * handles this for us asynchronously. More over, both sync and flush have the 311: * same meaning in this class, so calling flush has exactly the same effect. 312: * 313: * @see #flush 314: * @throws BackingStoreException if this operation cannot be completed due to 315: * a failure in the backing store, or inability to communicate with 316: * it. 317: */ 318: public void sync() throws BackingStoreException 319: { 320: this.flush(); 321: } 322: 323: /** 324: * Request a sync. 325: * 326: * @see #sync 327: * @throws BackingStoreException if this operation cannot be completed due to 328: * a failure in the backing store, or inability to communicate with 329: * it. 330: */ 331: protected void syncSpi() throws BackingStoreException 332: { 333: this.sync(); 334: } 335: 336: /** 337: * Returns the value of the given key. 338: * If the keys does not have a value, or there is an error in the backing 339: * store, <code>null</code> is returned instead. 340: * 341: * @param key The key to retrieve. 342: * @return The value associated with the given key. 343: */ 344: protected String getSpi(String key) 345: { 346: return backend.getKey(this.getGConfKey(key)); 347: } 348: 349: /** 350: * Returns <code>true</code> if this preference node is a user node, 351: * <code>false</code> if is a system preference node. 352: * 353: * @return <code>true</code> if this preference node is a user node, 354: * <code>false</code> if is a system preference node. 355: */ 356: public boolean isUserNode() 357: { 358: return this.isUser; 359: } 360: 361: /* 362: * PRIVATE METHODS 363: */ 364: 365: /** 366: * Builds a GConf key string suitable for operations on the backend. 367: * 368: * @param key The key to convert into a valid GConf key. 369: * @return A valid Gconf key. 370: */ 371: private String getGConfKey(String key) 372: { 373: String nodeName = ""; 374: 375: // strip key 376: // please, note that all names are unescaped into the native peer 377: key = GConfNativePeer.escapeString(key); 378: 379: if (this.node.endsWith("/")) 380: { 381: nodeName = this.node + key; 382: } 383: else 384: { 385: nodeName = this.node + "/" + key; 386: } 387: 388: return nodeName; 389: } 390: 391: /** 392: * Builds the root node to use for this preference. 393: * 394: * @param isUser Defines if this node is a user (<code>true</code>) or system 395: * (<code>false</code>) node. 396: * @return The real root of this preference tree. 397: */ 398: private String getRealRoot(boolean isUser) 399: { 400: // not sure about this, we should have already these permissions... 401: SecurityManager security = System.getSecurityManager(); 402: 403: if (security != null) 404: { 405: security.checkPermission(PERMISSION); 406: } 407: 408: String root = null; 409: 410: if (isUser) 411: { 412: root = System.getProperty("gnu.java.util.prefs.gconf.user_root", 413: DEFAULT_USER_ROOT); 414: } 415: else 416: { 417: root = System.getProperty("gnu.java.util.prefs.gconf.system_root", 418: DEFAULT_SYSTEM_ROOT); 419: } 420: 421: return root; 422: } 423: }