1
20 package org.crosswire.jsword.book.sword.state;
21
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.Queue;
26 import java.util.concurrent.ConcurrentLinkedQueue;
27 import java.util.concurrent.Executors;
28 import java.util.concurrent.ScheduledFuture;
29 import java.util.concurrent.ThreadFactory;
30 import java.util.concurrent.TimeUnit;
31
32 import org.crosswire.jsword.book.BookException;
33 import org.crosswire.jsword.book.BookMetaData;
34 import org.crosswire.jsword.book.sword.BlockType;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38
62 public final class OpenFileStateManager {
63
66 private OpenFileStateManager(final int cleanupIntervalSeconds, final int maxExpiry) {
67 this.monitoringThread = Executors.newScheduledThreadPool(1, new ThreadFactory() {
69 public Thread newThread(Runnable r) {
70 Thread t = new Thread(r);
71 t.setDaemon(true);
72 return t;
73 }
74
75 }).scheduleWithFixedDelay(new Runnable() {
76 public void run() {
77 long currentTime = System.currentTimeMillis();
80
81 for (Queue<OpenFileState> e : OpenFileStateManager.this.metaToStates.values()) {
82 for (Iterator<OpenFileState> iterator = e.iterator(); iterator.hasNext(); ) {
83 final OpenFileState state = iterator.next();
84 if (state.getLastAccess() + maxExpiry * 1000 < currentTime) {
85 state.releaseResources();
87
88 iterator.remove();
90 }
91 }
92 }
93 }
94 }, 0, cleanupIntervalSeconds, TimeUnit.SECONDS);
95 }
96
97
104 public static synchronized void init(final int cleanupIntervalSeconds, final int maxExpiry) {
105 if (manager == null) {
106 manager = new OpenFileStateManager(cleanupIntervalSeconds, maxExpiry);
107 } else {
108 LOGGER.warn("The OpenFileStateManager has already been initialised, potentially with its default settings. The following values were ignored: cleanUpInterval [{}], maxExpiry=[{}]", Integer.toString(cleanupIntervalSeconds), Integer.toString(maxExpiry));
110 }
111
112 }
113
114
118 public static OpenFileStateManager instance() {
119 if (manager == null) {
120 synchronized (OpenFileStateManager.class) {
121 init(60, 60);
122 }
123 }
124 return manager;
125 }
126
127 public RawBackendState getRawBackendState(BookMetaData metadata) throws BookException {
128 ensureNotShuttingDown();
129
130 RawBackendState state = getInstance(metadata);
131 if (state == null) {
132 LOGGER.trace("Initializing: {}", metadata.getInitials());
133 return new RawBackendState(metadata);
134 }
135
136 LOGGER.trace("Reusing: {}", metadata.getInitials());
137 return state;
138 }
139
140 public RawFileBackendState getRawFileBackendState(BookMetaData metadata) throws BookException {
141 ensureNotShuttingDown();
142
143 RawFileBackendState state = getInstance(metadata);
144 if (state == null) {
145 LOGGER.trace("Initializing: {}", metadata.getInitials());
146 return new RawFileBackendState(metadata);
147 }
148
149 LOGGER.trace("Reusing: {}", metadata.getInitials());
150 return state;
151 }
152
153 public GenBookBackendState getGenBookBackendState(BookMetaData metadata) throws BookException {
154 ensureNotShuttingDown();
155
156 GenBookBackendState state = getInstance(metadata);
157 if (state == null) {
158 LOGGER.trace("Initializing: {}", metadata.getInitials());
159 return new GenBookBackendState(metadata);
160 }
161
162 LOGGER.trace("Reusing: {}", metadata.getInitials());
163 return state;
164 }
165
166 public RawLDBackendState getRawLDBackendState(BookMetaData metadata) throws BookException {
167 ensureNotShuttingDown();
168
169 RawLDBackendState state = getInstance(metadata);
170 if (state == null) {
171 LOGGER.trace("Initializing: {}", metadata.getInitials());
172 return new RawLDBackendState(metadata);
173 }
174
175 LOGGER.trace("Reusing: {}", metadata.getInitials());
176 return state;
177 }
178
179 public ZLDBackendState getZLDBackendState(BookMetaData metadata) throws BookException {
180 ensureNotShuttingDown();
181
182 ZLDBackendState state = getInstance(metadata);
183 if (state == null) {
184 LOGGER.trace("Initializing: {}", metadata.getInitials());
185 return new ZLDBackendState(metadata);
186 }
187
188 LOGGER.trace("Reusing: {}", metadata.getInitials());
189 return state;
190 }
191
192 public ZVerseBackendState getZVerseBackendState(BookMetaData metadata, BlockType blockType) throws BookException {
193 ensureNotShuttingDown();
194
195 ZVerseBackendState state = getInstance(metadata);
196 if (state == null) {
197 LOGGER.trace("Initializing: {}", metadata.getInitials());
198 return new ZVerseBackendState(metadata, blockType);
199 }
200
201 LOGGER.trace("Reusing: {}", metadata.getInitials());
202 return state;
203 }
204
205 @SuppressWarnings("unchecked")
206 private <T extends OpenFileState> T getInstance(BookMetaData metadata) {
207 Queue<OpenFileState> availableStates = getQueueForMeta(metadata);
208 final T state = (T) availableStates.poll();
209
210 if (state != null) {
215 state.setLastAccess(System.currentTimeMillis());
216 }
217 return state;
218 }
219
220 private Queue<OpenFileState> getQueueForMeta(BookMetaData metadata) {
221 Queue<OpenFileState> availableStates = metaToStates.get(metadata);
222 if (availableStates == null) {
223 synchronized (OpenFileState.class) {
224 availableStates = new ConcurrentLinkedQueue<OpenFileState>();
225 metaToStates.put(metadata, availableStates);
226 }
227 }
228 return availableStates;
229 }
230
231 public void release(OpenFileState fileState) {
232 if (fileState == null) {
233 return;
236 }
237
238 fileState.setLastAccess(System.currentTimeMillis());
239
240 BookMetaData bmd = fileState.getBookMetaData();
242 Queue<OpenFileState> queueForMeta = getQueueForMeta(bmd);
243 LOGGER.trace("Offering to releasing: {}", bmd.getInitials());
244 boolean offered = queueForMeta.offer(fileState);
245
246 if (!offered) {
248 LOGGER.trace("Released: {}", bmd.getInitials());
249 fileState.releaseResources();
250 }
251 }
252
253
256 public void shutDown() {
257 shuttingDown = true;
258 this.monitoringThread.cancel(true);
259 for (Queue<OpenFileState> e : metaToStates.values()) {
260 OpenFileState state = null;
261 while ((state = e.poll()) != null) {
262 state.releaseResources();
263 }
264 }
265 }
266
267 private void ensureNotShuttingDown() throws BookException {
268 if (shuttingDown) {
269 throw new BookException("Unable to read book, application is shutting down.");
270 }
271 }
272
273 private final ScheduledFuture<?> monitoringThread;
274 private final Map<BookMetaData, Queue<OpenFileState>> metaToStates = new HashMap<BookMetaData, Queue<OpenFileState>>();
275 private volatile boolean shuttingDown;
276
277 private static volatile OpenFileStateManager manager;
278 private static final Logger LOGGER = LoggerFactory.getLogger(OpenFileStateManager.class);
279 }
280