Index: src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- src/org/openstreetmap/josm/data/Preferences.java	(revision 1594)
+++ src/org/openstreetmap/josm/data/Preferences.java	(working copy)
@@ -259,6 +259,10 @@
     synchronized public boolean putDouble(final String key, final Double value) {
         return put(key, Double.toString(value));
     }
+    
+    synchronized public boolean putLong(final String key, final Long value) {
+        return put(key, Long.toString(value));
+    }
 
     private final void firePreferenceChanged(final String key, final String value) {
         for (final PreferenceChangedListener l : listener)
Index: src/org/openstreetmap/josm/io/CacheCustomContent.java
===================================================================
--- src/org/openstreetmap/josm/io/CacheCustomContent.java	(revision 1594)
+++ src/org/openstreetmap/josm/io/CacheCustomContent.java	(working copy)
@@ -10,6 +10,13 @@
 
 import org.openstreetmap.josm.Main;
 
+/**
+ * Use this class if you want to cache and store a single file that gets updated regularly.
+ * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files,
+ * use CacheFiles
+ * @author xeen
+ *
+ */
 public abstract class CacheCustomContent {
     /**
      * Common intervals
Index: src/org/openstreetmap/josm/io/CacheFiles.java
===================================================================
--- src/org/openstreetmap/josm/io/CacheFiles.java	(revision 0)
+++ src/org/openstreetmap/josm/io/CacheFiles.java	(revision 0)
@@ -0,0 +1,336 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeMap;
+
+import javax.imageio.ImageIO;
+
+import org.openstreetmap.josm.Main;
+
+/**
+ * Use this class if you want to cache a lot of files that shouldn't be kept in memory. You can
+ * specify how much data should be stored and after which date the files should be expired.
+ * This works on a last-access basis, so files get deleted after they haven't been used for x days.
+ * You can turn this off by calling setUpdateModTime(false). Files get deleted on a first-in-first-out
+ * basis.
+ * @author xeen
+ *
+ */
+public class CacheFiles {
+    /**
+     * Common expirey dates
+     */
+    final static public int EXPIRE_NEVER = -1;
+    final static public int EXPIRE_DAILY = 60 * 60 * 24;
+    final static public int EXPIRE_WEEKLY = EXPIRE_DAILY * 7;
+    final static public int EXPIRE_MONTHLY = EXPIRE_WEEKLY * 4;
+    
+    final private File dir;
+    final private String ident;
+    final private boolean enabled;
+    
+    private long expire;  // in seconds
+    private long maxsize; // in megabytes
+    private boolean updateModTime = true;
+    
+    // If the cache is full, we don't want to delete just one file
+    private final int cleanUpThreshold = 20;
+    // We don't want to clean after every file-write
+    private final int cleanUpInterval = 5;
+    // Stores how many files have been written
+    private int writes = 0;
+    
+    /**
+     * Creates a new cache class. The ident will be used to store the files on disk and to save
+     * expire/space settings.
+     * @param ident
+     */
+    public CacheFiles(String ident) {
+       String pref = Main.pref.getPluginsDirFile().getPath();
+       boolean dir_writeable;
+       this.ident = ident;
+       this.dir = new File(pref + "/" + ident + "/cache/");
+       try {
+           this.dir.mkdirs();
+           dir_writeable = true;
+        } catch(Exception e) {
+           // We have no access to this directory, so don't to anything
+           dir_writeable = false;
+        }
+        this.enabled = dir_writeable;
+        this.expire = Main.pref.getLong("cache." + ident + "." + "expire", EXPIRE_DAILY);
+        if(this.expire < 0)
+            this.expire = CacheFiles.EXPIRE_NEVER;
+        this.maxsize = Main.pref.getLong("cache." + ident + "." + "maxsize", 50);
+        if(this.maxsize < 0)
+            this.maxsize = -1;
+    }
+    
+    /**
+     * Loads the data for the given ident as an byte array. Returns null if data not available.
+     * @param ident
+     * @return
+     */
+    public byte[] getData(String ident) {
+        if(!enabled) return null;
+        try {
+            File data = getPath(ident);
+            if(!data.exists())
+                return null;
+            
+            if(isExpired(data)) {
+                data.delete();
+                return null;
+            }
+            
+            // Update last mod time so we don't expire recently used data
+            if(updateModTime)
+                data.setLastModified(new Date().getTime());
+            
+            byte[] bytes = new byte[(int) data.length()];
+            new RandomAccessFile(data, "r").readFully(bytes);
+            return bytes;
+        } catch(Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return null;
+    }
+    
+    /**
+     * Writes an byte-array to disk
+     * @param ident
+     * @param data
+     */
+    public void saveData(String ident, byte[] data) {
+        if(!enabled) return;
+        try {
+            File f = getPath(ident);
+            if(f.exists())
+                f.delete();
+            // rws also updates the file meta-data, i.e. last mod time
+            new RandomAccessFile(f, "rws").write(data);
+        } catch(Exception e){
+            System.out.println(e.getMessage());
+        }
+        
+        writes++;
+        checkCleanUp();
+    }    
+    
+    /**
+     * Loads the data for the given ident as an image. If no image is found, null is returned
+     * @param ident Identifier
+     * @return BufferedImage or null
+     */
+    public BufferedImage getImg(String ident) {
+        if(!enabled) return null;
+        try {
+            File img = getPath(ident, "png");
+            if(!img.exists())
+                return null;
+            
+            if(isExpired(img)) {
+                img.delete();
+                return null;
+            }
+            // Update last mod time so we don't expire recently used images
+            if(updateModTime)
+                img.setLastModified(new Date().getTime());
+            return ImageIO.read(img);
+        } catch(Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Saves a given image and ident to the cache
+     * @param ident
+     * @param image
+     */
+    public void saveImg(String ident, BufferedImage image) {
+        if(!enabled) return;
+        try {
+            ImageIO.write(image, "png", getPath(ident, "png"));
+        } catch(Exception e){
+            System.out.println(e.getMessage());
+        }
+        
+        writes++;
+        checkCleanUp();
+    }
+    
+    
+    /**
+     * Sets the amount of time data is stored before it gets expired
+     * @param amount of time in seconds
+     * @param force will also write it to the preferences
+     */
+    public void setExpire(int amount, boolean force) {
+        if(amount < 0)
+            this.expire = EXPIRE_NEVER;
+        else
+            this.expire = amount;
+        String key = "cache." + ident + "." + "expire";
+        if(force || !Main.pref.hasKey(key))
+            Main.pref.putLong(key, this.expire);
+    }
+    
+    /**
+     * Sets the amount of data stored in the cache
+     * @param amount in Megabytes
+     * @param force will also write it to the preferences
+     */
+    public void setMaxSize(int amount, boolean force) {
+        this.maxsize = amount > 0 ? amount : -1;
+        String key = "cache." + ident + "." + "maxsize";
+        if(force || !Main.pref.hasKey(key))
+            Main.pref.putLong(key, this.maxsize);
+    }
+    
+    /**
+     * Call this with true to update the last modification time when a file it is read.
+     * Call this with false to not update the last modification time when a file is read.
+     * @param to
+     */
+    public void setUpdateModTime(boolean to) {
+        updateModTime = to;
+    }
+    
+    /**
+     * Checks if a clean up is needed and will do so if necessary
+     */
+    public void checkCleanUp() {
+        if(this.writes > this.cleanUpInterval)
+            cleanUp();
+    }
+    
+    /**
+     * Performs a default clean up with the set values (deletes oldest files first) 
+     */
+    public void cleanUp() {
+        if(!this.enabled || maxsize == -1) return;
+
+        TreeMap<Long, File> modtime = new TreeMap<Long, File>();
+        long dirsize = 0;
+
+        for(File f : dir.listFiles()) {
+            if(isExpired(f))
+                f.delete();
+            else {
+                dirsize += f.length();
+                modtime.put(f.lastModified(), f);
+            }
+        }
+
+        if(dirsize < maxsize*1000*1000) return;
+
+        Set<Long> keySet = modtime.keySet();
+        Iterator<Long> it = keySet.iterator();
+        int i=0;
+        while (it.hasNext()) {
+            i++;
+            modtime.get(it.next()).delete();
+
+            // Delete a couple of files, then check again
+            if(i % cleanUpThreshold == 0 && getDirSize() < maxsize)
+                return;
+        } 
+        writes = 0;
+    }
+    
+    final static public int CLEAN_ALL = 0;
+    final static public int CLEAN_SMALL_FILES = 1;
+    final static public int CLEAN_BY_DATE = 2;    
+    /**
+     * Performs a non-default, specified clean up
+     * @param type any of the CLEAN_XX constants.
+     * @param size for CLEAN_SMALL_FILES: deletes all files smaller than (size) bytes
+     */
+    public void customCleanUp(int type, int size) {
+        switch(type) {
+            case CLEAN_ALL:
+                for(File f : dir.listFiles())
+                    f.delete();
+                break;
+            case CLEAN_SMALL_FILES:
+                for(File f: dir.listFiles())
+                    if(f.length() < size)
+                        f.delete();
+                break;
+            case CLEAN_BY_DATE:
+                cleanUp();
+                break;
+        }
+    }
+    
+    /**
+     * Calculates the size of the directory
+     * @return long Size of directory in bytes
+     */
+    private long getDirSize() {
+        if(!enabled) return -1;
+        long dirsize = 0;
+
+        for(File f : this.dir.listFiles())
+            dirsize += f.length();
+        return dirsize;
+    }
+    
+    /**
+     * Returns a short and unique file name for a given long identifier
+     * @return String short filename
+     */
+    private static String getUniqueFilename(String ident) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            BigInteger number = new BigInteger(1, md.digest(ident.getBytes()));
+            return number.toString(16);
+        } catch(Exception e) {
+            // Fall back. Remove unsuitable characters and some random ones to shrink down path length.
+            // Limit it to 70 characters, that leaves about 190 for the path on Windows/NTFS
+            ident = ident.replaceAll("[^a-zA-Z0-9]", "");
+            ident = ident.replaceAll("[acegikmoqsuwy]", "");
+            return ident.substring(ident.length() - 70);
+        }
+    }
+    
+    /**
+     * Gets file path for ident with customizable file-ending
+     * @param ident
+     * @param ending
+     * @return File
+     */
+    private File getPath(String ident, String ending) {
+        return new File(dir, getUniqueFilename(ident) + "." + ending);
+    }
+    
+    /**
+     * Gets file path for ident
+     * @param ident
+     * @param ending
+     * @return File
+     */
+    private File getPath(String ident) {
+        return new File(dir, getUniqueFilename(ident));
+    }
+    
+    /**
+     * Checks whether a given file is expired
+     * @param file
+     * @return expired?
+     */
+    private boolean isExpired(File file) {
+        if(CacheFiles.EXPIRE_NEVER == this.expire)
+            return false;
+        return (file.lastModified() < (new Date().getTime() - expire*1000));
+    }
+}
