001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.io; 003 004 import java.io.BufferedInputStream; 005 import java.io.BufferedOutputStream; 006 import java.io.File; 007 import java.io.FileInputStream; 008 import java.io.FileOutputStream; 009 import java.io.IOException; 010 import java.io.UnsupportedEncodingException; 011 import java.util.Date; 012 013 import org.openstreetmap.josm.Main; 014 015 /** 016 * Use this class if you want to cache and store a single file that gets updated regularly. 017 * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files, 018 * use CacheFiles 019 * @param <T> a {@link Throwable} that may be thrown during {@link #updateData()}, 020 * use {@link RuntimeException} if no exception must be handled. 021 * @author xeen 022 * 023 */ 024 public abstract class CacheCustomContent<T extends Throwable> { 025 /** 026 * Common intervals 027 */ 028 final static public int INTERVAL_ALWAYS = -1; 029 final static public int INTERVAL_HOURLY = 60*60; 030 final static public int INTERVAL_DAILY = INTERVAL_HOURLY * 24; 031 final static public int INTERVAL_WEEKLY = INTERVAL_DAILY * 7; 032 final static public int INTERVAL_MONTHLY = INTERVAL_WEEKLY * 4; 033 final static public int INTERVAL_NEVER = Integer.MAX_VALUE; 034 035 /** 036 * Where the data will be stored 037 */ 038 private byte[] data = null; 039 040 /** 041 * The ident that identifies the stored file. Includes file-ending. 042 */ 043 final private String ident; 044 045 /** 046 * The (file-)path where the data will be stored 047 */ 048 final private File path; 049 050 /** 051 * How often to update the cached version 052 */ 053 final private int updateInterval; 054 055 /** 056 * This function will be executed when an update is required. It has to be implemented by the 057 * inheriting class and should use a worker if it has a long wall time as the function is 058 * executed in the current thread. 059 * @return the data to cache 060 */ 061 protected abstract byte[] updateData() throws T; 062 063 /** 064 * This function serves as a comfort hook to perform additional checks if the cache is valid 065 * @return True if the cached copy is still valid 066 */ 067 protected boolean isCacheValid() { 068 return true; 069 } 070 071 /** 072 * Initializes the class. Note that all read data will be stored in memory until it is flushed 073 * by flushData(). 074 * @param ident 075 * @param updateInterval 076 */ 077 public CacheCustomContent(String ident, int updateInterval) { 078 this.ident = ident; 079 this.updateInterval = updateInterval; 080 this.path = new File(Main.pref.getCacheDirectory(), ident); 081 } 082 083 /** 084 * Updates data if required 085 * @return Returns the data 086 */ 087 public byte[] updateIfRequired() throws T { 088 if (Main.pref.getInteger("cache." + ident, 0) + updateInterval < new Date().getTime()/1000 089 || !isCacheValid()) 090 return updateForce(); 091 return getData(); 092 } 093 094 /** 095 * Updates data if required 096 * @return Returns the data as string 097 */ 098 public String updateIfRequiredString() throws T { 099 if (Main.pref.getInteger("cache." + ident, 0) + updateInterval < new Date().getTime()/1000 100 || !isCacheValid()) 101 return updateForceString(); 102 return getDataString(); 103 } 104 105 /** 106 * Executes an update regardless of updateInterval 107 * @return Returns the data 108 */ 109 public byte[] updateForce() throws T { 110 this.data = updateData(); 111 saveToDisk(); 112 Main.pref.putInteger("cache." + ident, (int)(new Date().getTime()/1000)); 113 return data; 114 } 115 116 /** 117 * Executes an update regardless of updateInterval 118 * @return Returns the data as String 119 */ 120 public String updateForceString() throws T { 121 updateForce(); 122 try { 123 return new String(data, "utf-8"); 124 } catch (UnsupportedEncodingException e){ 125 e.printStackTrace(); 126 return ""; 127 } 128 } 129 130 /** 131 * Returns the data without performing any updates 132 * @return the data 133 */ 134 public byte[] getData() throws T { 135 if (data == null) { 136 loadFromDisk(); 137 } 138 return data; 139 } 140 141 /** 142 * Returns the data without performing any updates 143 * @return the data as String 144 */ 145 public String getDataString() throws T { 146 try { 147 return new String(getData(), "utf-8"); 148 } catch(UnsupportedEncodingException e){ 149 e.printStackTrace(); 150 return ""; 151 } 152 } 153 154 /** 155 * Tries to load the data using the given ident from disk. If this fails, data will be updated 156 */ 157 private void loadFromDisk() throws T { 158 if (Main.applet) 159 this.data = updateForce(); 160 else { 161 try { 162 BufferedInputStream input = new BufferedInputStream(new FileInputStream(path)); 163 this.data = new byte[input.available()]; 164 input.read(this.data); 165 input.close(); 166 } catch (IOException e) { 167 this.data = updateForce(); 168 } 169 } 170 } 171 172 /** 173 * Stores the data to disk 174 */ 175 private void saveToDisk() { 176 if (Main.applet) 177 return; 178 try { 179 BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(path)); 180 output.write(this.data); 181 output.flush(); 182 output.close(); 183 } catch(Exception e) { 184 e.printStackTrace(); 185 } 186 } 187 188 /** 189 * Flushes the data from memory. Class automatically reloads it from disk or updateData() if 190 * required 191 */ 192 public void flushData() { 193 data = null; 194 } 195 }