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