Source for gnu.java.util.prefs.FileBasedPreferences

   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:    */
  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:    */
 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: 
 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: 
 155:   public boolean isUserNode()
 156:   {
 157:     // For now file preferences are always user nodes.
 158:     return true;
 159:   }
 160: 
 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: 
 176:   protected AbstractPreferences childSpi(String name)
 177:   {
 178:     return new FileBasedPreferences(this, name);
 179:   }
 180: 
 181:   protected String[] keysSpi() throws BackingStoreException
 182:   {
 183:     return (String[]) properties.keySet().toArray(new String[0]);
 184:   }
 185: 
 186:   protected String getSpi(String key)
 187:   {
 188:     return properties.getProperty(key);
 189:   }
 190: 
 191:   protected void putSpi(String key, String value)
 192:   {
 193:     properties.put(key, value);
 194:   }
 195: 
 196:   protected void removeSpi(String key)
 197:   {
 198:     properties.remove(key);
 199:   }
 200: 
 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: 
 262:   protected void syncSpi() throws BackingStoreException
 263:   {
 264:     // FIXME: we ought to synchronize but instead we merely flush.
 265:     flushSpi();
 266:   }
 267: 
 268:   protected void removeNodeSpi() throws BackingStoreException
 269:   {
 270:     // We can simply delegate.
 271:     flushSpi();
 272:   }
 273: }