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 }