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 /** 083 * Updates data if required 084 * @return Returns the data 085 */ 086 public byte[] updateIfRequired() throws T { 087 if (Main.pref.getInteger("cache." + ident, 0) + updateInterval < System.currentTimeMillis()/1000 088 || !isCacheValid()) 089 return updateForce(); 090 return getData(); 091 } 092 093 /** 094 * Updates data if required 095 * @return Returns the data as string 096 */ 097 public String updateIfRequiredString() throws T { 098 if (Main.pref.getInteger("cache." + ident, 0) + updateInterval < System.currentTimeMillis()/1000 099 || !isCacheValid()) 100 return updateForceString(); 101 return getDataString(); 102 } 103 104 /** 105 * Executes an update regardless of updateInterval 106 * @return Returns the data 107 */ 108 public byte[] updateForce() throws T { 109 this.data = updateData(); 110 saveToDisk(); 111 Main.pref.putInteger("cache." + ident, (int)(System.currentTimeMillis()/1000)); 112 return data; 113 } 114 115 /** 116 * Executes an update regardless of updateInterval 117 * @return Returns the data as String 118 */ 119 public String updateForceString() throws T { 120 updateForce(); 121 return new String(data, StandardCharsets.UTF_8); 122 } 123 124 /** 125 * Returns the data without performing any updates 126 * @return the data 127 */ 128 public byte[] getData() throws T { 129 if (data == null) { 130 loadFromDisk(); 131 } 132 return data; 133 } 134 135 /** 136 * Returns the data without performing any updates 137 * @return the data as String 138 */ 139 public String getDataString() throws T { 140 return new String(getData(), StandardCharsets.UTF_8); 141 } 142 143 /** 144 * Tries to load the data using the given ident from disk. If this fails, data will be updated 145 */ 146 private void loadFromDisk() throws T { 147 try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(path))) { 148 this.data = new byte[input.available()]; 149 input.read(this.data); 150 } catch (IOException e) { 151 this.data = updateForce(); 152 } 153 } 154 155 /** 156 * Stores the data to disk 157 */ 158 private void saveToDisk() { 159 try (BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(path))) { 160 output.write(this.data); 161 output.flush(); 162 } catch (IOException e) { 163 Main.error(e); 164 } 165 } 166 167 /** 168 * Flushes the data from memory. Class automatically reloads it from disk or updateData() if 169 * required 170 */ 171 public void flushData() { 172 data = null; 173 } 174}