1 /* FileBasedPreferences.java -- File-based preference 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 39 package gnu.java.util.prefs; 40 41 import gnu.classpath.SystemProperties; 42 43 import java.io.File; 44 import java.io.FileInputStream; 45 import java.io.FileOutputStream; 46 import java.io.FilenameFilter; 47 import java.io.IOException; 48 import java.nio.channels.FileChannel; 49 import java.nio.channels.FileLock; 50 import java.util.Properties; 51 import java.util.prefs.AbstractPreferences; 52 import java.util.prefs.BackingStoreException; 53 54 /** 55 * This is a simple file-based preference implementation which writes 56 * the preferences as properties files. A node is represented as a directory 57 * beneath the user's home directory. The preferences for the node are 58 * stored in a single properties file in that directory. Sub-nodes are 59 * stored in subdirectories. This implementation uses file locking to 60 * mediate access to the properties files. 61 */ 62 public class FileBasedPreferences 63 extends AbstractPreferences 64 { 65 /** 66 * Name of the property file storing the data in a given directory. 67 */ 68 private static final String DATA_FILE = "data.properties"; 69 70 /** 71 * The directory corresponding to this preference node. 72 */ 73 private File directory; 74 75 /** 76 * The file holding the data for this node. 77 */ 78 private File dataFile; 79 80 /** 81 * The data in this node. 82 */ 83 private Properties properties; 84 85 /** 86 * Create the root node for the file-based preferences. 87 */ FileBasedPreferences()88 FileBasedPreferences() 89 { 90 super(null, ""); 91 String home = SystemProperties.getProperty("user.home"); 92 this.directory = new File(new File(home, ".classpath"), "userPrefs"); 93 this.dataFile = new File(this.directory, DATA_FILE); 94 load(); 95 } 96 97 /** 98 * Create a new file-based preference object with the given parent 99 * and the given name. 100 * @param parent the parent 101 * @param name the name of this node 102 */ FileBasedPreferences(FileBasedPreferences parent, String name)103 FileBasedPreferences(FileBasedPreferences parent, String name) 104 { 105 super(parent, name); 106 this.directory = new File(parent.directory, name); 107 this.dataFile = new File(this.directory, DATA_FILE); 108 load(); 109 } 110 load()111 private void load() 112 { 113 this.properties = new Properties(); 114 FileInputStream fis = null; 115 FileLock lock = null; 116 try 117 { 118 fis = new FileInputStream(this.dataFile); 119 FileChannel channel = fis.getChannel(); 120 lock = channel.lock(0, Long.MAX_VALUE, true); 121 this.properties.load(fis); 122 // We release the lock and close the stream in the 'finally' 123 // clause. 124 } 125 catch (IOException _) 126 { 127 // We don't mind; this means we're making a new node. 128 newNode = true; 129 } 130 finally 131 { 132 try 133 { 134 // Release the lock and close the stream. 135 if (lock != null) 136 lock.release(); 137 } 138 catch (IOException ignore) 139 { 140 // Ignore. 141 } 142 try 143 { 144 // Close the stream. 145 if (fis != null) 146 fis.close(); 147 } 148 catch (IOException ignore) 149 { 150 // Ignore. 151 } 152 } 153 } 154 isUserNode()155 public boolean isUserNode() 156 { 157 // For now file preferences are always user nodes. 158 return true; 159 } 160 childrenNamesSpi()161 protected String[] childrenNamesSpi() throws BackingStoreException 162 { 163 // FIXME: security manager. 164 String[] result = directory.list(new FilenameFilter() 165 { 166 public boolean accept(File dir, String name) 167 { 168 return new File(dir, name).isDirectory(); 169 } 170 }); 171 if (result == null) 172 result = new String[0]; 173 return result; 174 } 175 childSpi(String name)176 protected AbstractPreferences childSpi(String name) 177 { 178 return new FileBasedPreferences(this, name); 179 } 180 keysSpi()181 protected String[] keysSpi() throws BackingStoreException 182 { 183 return (String[]) properties.keySet().toArray(new String[0]); 184 } 185 getSpi(String key)186 protected String getSpi(String key) 187 { 188 return properties.getProperty(key); 189 } 190 putSpi(String key, String value)191 protected void putSpi(String key, String value) 192 { 193 properties.put(key, value); 194 } 195 removeSpi(String key)196 protected void removeSpi(String key) 197 { 198 properties.remove(key); 199 } 200 flushSpi()201 protected void flushSpi() throws BackingStoreException 202 { 203 // FIXME: security manager. 204 try 205 { 206 if (isRemoved()) 207 { 208 // Delete the underlying file. 209 // FIXME: ideally we would also delete the directory 210 // if it had no subdirectories. This doesn't matter 211 // much though. 212 // FIXME: there's a strange race here if a different VM is 213 // simultaneously updating this node. 214 dataFile.delete(); 215 } 216 else 217 { 218 // Write the underlying file. 219 directory.mkdirs(); 220 221 FileOutputStream fos = null; 222 FileLock lock = null; 223 try 224 { 225 // Note that we let IOExceptions from the try clause 226 // propagate to the outer 'try'. 227 fos = new FileOutputStream(dataFile); 228 FileChannel channel = fos.getChannel(); 229 lock = channel.lock(); 230 properties.store(fos, "created by GNU Classpath FileBasedPreferences"); 231 // Lock is released and file closed in the finally clause. 232 } 233 finally 234 { 235 try 236 { 237 if (lock != null) 238 lock.release(); 239 } 240 catch (IOException _) 241 { 242 // Ignore. 243 } 244 try 245 { 246 if (fos != null) 247 fos.close(); 248 } 249 catch (IOException _) 250 { 251 // Ignore. 252 } 253 } 254 } 255 } 256 catch (IOException ioe) 257 { 258 throw new BackingStoreException(ioe); 259 } 260 } 261 syncSpi()262 protected void syncSpi() throws BackingStoreException 263 { 264 // FIXME: we ought to synchronize but instead we merely flush. 265 flushSpi(); 266 } 267 removeNodeSpi()268 protected void removeNodeSpi() throws BackingStoreException 269 { 270 // We can simply delegate. 271 flushSpi(); 272 } 273 } 274