source: josm/trunk/src/org/openstreetmap/josm/io/CacheCustomContent.java @ 12620

Last change on this file since 12620 was 12620, checked in by Don-vip, 4 months ago

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

  • Property svn:eol-style set to native
File size: 6.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.io.BufferedInputStream;
5import java.io.BufferedOutputStream;
6import java.io.File;
7import java.io.FileInputStream;
8import java.io.FileOutputStream;
9import java.io.IOException;
10import java.nio.charset.StandardCharsets;
11import java.util.concurrent.TimeUnit;
12
13import org.openstreetmap.josm.Main;
14import org.openstreetmap.josm.tools.Logging;
15import org.openstreetmap.josm.tools.Utils;
16
17/**
18 * Use this class if you want to cache and store a single file that gets updated regularly.
19 * Unless you flush() it will be kept in memory. If you want to cache a lot of data and/or files, use CacheFiles.
20 * @author xeen
21 * @param <T> a {@link Throwable} that may be thrown during {@link #updateData()},
22 * use {@link RuntimeException} if no exception must be handled.
23 * @since 1450
24 */
25public abstract class CacheCustomContent<T extends Throwable> {
26
27    /** Update interval meaning an update is always needed */
28    public static final int INTERVAL_ALWAYS = -1;
29    /** Update interval meaning an update is needed each hour */
30    public static final int INTERVAL_HOURLY = (int) TimeUnit.HOURS.toSeconds(1);
31    /** Update interval meaning an update is needed each day */
32    public static final int INTERVAL_DAILY = (int) TimeUnit.DAYS.toSeconds(1);
33    /** Update interval meaning an update is needed each week */
34    public static final int INTERVAL_WEEKLY = (int) TimeUnit.DAYS.toSeconds(7);
35    /** Update interval meaning an update is needed each month */
36    public static final int INTERVAL_MONTHLY = (int) TimeUnit.DAYS.toSeconds(28);
37    /** Update interval meaning an update is never needed */
38    public static final int INTERVAL_NEVER = Integer.MAX_VALUE;
39
40    /**
41     * Where the data will be stored
42     */
43    private byte[] data;
44
45    /**
46     * The ident that identifies the stored file. Includes file-ending.
47     */
48    private final String ident;
49
50    /**
51     * The (file-)path where the data will be stored
52     */
53    private final File path;
54
55    /**
56     * How often to update the cached version
57     */
58    private final int updateInterval;
59
60    /**
61     * This function will be executed when an update is required. It has to be implemented by the
62     * inheriting class and should use a worker if it has a long wall time as the function is
63     * executed in the current thread.
64     * @return the data to cache
65     * @throws T a {@link Throwable}
66     */
67    protected abstract byte[] updateData() throws T;
68
69    /**
70     * Initializes the class. Note that all read data will be stored in memory until it is flushed
71     * by flushData().
72     * @param ident ident that identifies the stored file. Includes file-ending.
73     * @param updateInterval update interval in seconds. -1 means always
74     */
75    public CacheCustomContent(String ident, int updateInterval) {
76        this.ident = ident;
77        this.updateInterval = updateInterval;
78        this.path = new File(Main.pref.getCacheDirectory(), ident);
79    }
80
81    /**
82     * This function serves as a comfort hook to perform additional checks if the cache is valid
83     * @return True if the cached copy is still valid
84     */
85    protected boolean isCacheValid() {
86        return true;
87    }
88
89    private boolean needsUpdate() {
90        if (isOffline()) {
91            return false;
92        }
93        return Main.pref.getInteger("cache." + ident, 0) + updateInterval < TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
94                || !isCacheValid();
95    }
96
97    private boolean isOffline() {
98        try {
99            checkOfflineAccess();
100            return false;
101        } catch (OfflineAccessException e) {
102            Logging.trace(e);
103            return true;
104        }
105    }
106
107    /**
108     * Ensures underlying resource is not accessed in offline mode.
109     * @throws OfflineAccessException if resource is accessed in offline mode
110     */
111    protected abstract void checkOfflineAccess();
112
113    /**
114     * Updates data if required
115     * @return Returns the data
116     * @throws T if an error occurs
117     */
118    public byte[] updateIfRequired() throws T {
119        if (needsUpdate())
120            return updateForce();
121        return getData();
122    }
123
124    /**
125     * Updates data if required
126     * @return Returns the data as string
127     * @throws T if an error occurs
128     */
129    public String updateIfRequiredString() throws T {
130        if (needsUpdate())
131            return updateForceString();
132        return getDataString();
133    }
134
135    /**
136     * Executes an update regardless of updateInterval
137     * @return Returns the data
138     * @throws T if an error occurs
139     */
140    private byte[] updateForce() throws T {
141        this.data = updateData();
142        saveToDisk();
143        Main.pref.putInteger("cache." + ident, (int) (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
144        return data;
145    }
146
147    /**
148     * Executes an update regardless of updateInterval
149     * @return Returns the data as String
150     * @throws T if an error occurs
151     */
152    public String updateForceString() throws T {
153        updateForce();
154        return new String(data, StandardCharsets.UTF_8);
155    }
156
157    /**
158     * Returns the data without performing any updates
159     * @return the data
160     * @throws T if an error occurs
161     */
162    public byte[] getData() throws T {
163        if (data == null) {
164            loadFromDisk();
165        }
166        return Utils.copyArray(data);
167    }
168
169    /**
170     * Returns the data without performing any updates
171     * @return the data as String
172     * @throws T if an error occurs
173     */
174    public String getDataString() throws T {
175        byte[] array = getData();
176        if (array == null) {
177            return null;
178        }
179        return new String(array, StandardCharsets.UTF_8);
180    }
181
182    /**
183     * Tries to load the data using the given ident from disk. If this fails, data will be updated, unless run in offline mode
184     * @throws T a {@link Throwable}
185     */
186    private void loadFromDisk() throws T {
187        try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(path))) {
188            this.data = new byte[input.available()];
189            if (input.read(this.data) < this.data.length) {
190                Logging.error("Failed to read expected contents from "+path);
191            }
192        } catch (IOException e) {
193            Logging.trace(e);
194            if (!isOffline()) {
195                this.data = updateForce();
196            }
197        }
198    }
199
200    /**
201     * Stores the data to disk
202     */
203    private void saveToDisk() {
204        try (BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(path))) {
205            output.write(this.data);
206            output.flush();
207        } catch (IOException e) {
208            Logging.error(e);
209        }
210    }
211
212    /**
213     * Flushes the data from memory. Class automatically reloads it from disk or updateData() if required
214     */
215    public void flushData() {
216        data = null;
217    }
218}
Note: See TracBrowser for help on using the repository browser.