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