001package org.cache2k.impl;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import org.cache2k.MutableCacheEntry;
026import org.cache2k.storage.StorageEntry;
027
028/**
029 * The cache entry. This is a combined hashtable entry with hashCode and
030 * and collision list (other field) and it contains a double linked list
031 * (next and previous) for the eviction algorithm.
032 *
033 * @author Jens Wilke
034 */
035@SuppressWarnings("unchecked")
036public class Entry<E extends Entry, K, T>
037  implements MutableCacheEntry<K,T>, StorageEntry {
038
039  static final int FETCHED_STATE = 16;
040  static final int REFRESH_STATE = FETCHED_STATE + 1;
041  static final int REPUT_STATE = FETCHED_STATE + 3;
042
043  static final int FETCH_IN_PROGRESS_VALID = FETCHED_STATE + 4;
044
045  static final int LOADED_NON_VALID_AND_PUT = 9;
046
047  static final int FETCH_ABORT = 8;
048
049  static final int FETCH_IN_PROGRESS_NON_VALID = 7;
050
051  /** Storage was checked, no data available */
052  static final int LOADED_NON_VALID_AND_FETCH = 6;
053
054  /** Storage was checked, no data available */
055  static final int LOADED_NON_VALID = 5;
056
057  static final int EXPIRED_STATE = 4;
058
059  /** Logically the same as immediately expired */
060  static final int FETCH_NEXT_TIME_STATE = 3;
061
062  static private final int REMOVED_STATE = 2;
063
064  static private final int FETCH_IN_PROGRESS_VIRGIN = 1;
065
066  static final int VIRGIN_STATE = 0;
067
068  static final int EXPIRY_TIME_MIN = 32;
069
070  static private final StaleMarker STALE_MARKER_KEY = new StaleMarker();
071
072  final static InitialValueInEntryNeverReturned INITIAL_VALUE = new InitialValueInEntryNeverReturned();
073
074  public BaseCache.MyTimerTask task;
075
076  /**
077   * Time the entry was last updated by put or by fetching it from the cache source.
078   * The time is the time in millis times 2. A set bit 1 means the entry is fetched from
079   * the storage and not modified since then.
080   */
081  public long fetchedTime;
082
083  /**
084   * Contains the next time a refresh has to occur. Low values have a special meaning, see defined constants.
085   * Negative values means the refresh time was expired, and we need to check the time.
086   */
087  public volatile long nextRefreshTime;
088
089  public K key;
090
091  public volatile T value = (T) INITIAL_VALUE;
092
093  /**
094   * Hash implementation: the calculated, modified hash code, retrieved from the key when the entry is
095   * inserted in the cache
096   *
097   * @see BaseCache#modifiedHash(int)
098   */
099  public int hashCode;
100
101  /**
102   * Hash implementation: Link to another entry in the same hash table slot when the hash code collides.
103   */
104  public Entry<E, K, T> another;
105
106  /** Lru list: pointer to next element or list head */
107  public E next;
108  /** Lru list: pointer to previous element or list head */
109  public E prev;
110
111  public void setLastModification(long t) {
112    fetchedTime = t << 1;
113  }
114
115  /**
116   * Memory entry needs to be send to the storage.
117   */
118  public boolean isDirty() {
119    return (fetchedTime & 1) == 0;
120  }
121
122  public void setLastModificationFromStorage(long t) {
123    fetchedTime = t << 1 | 1;
124  }
125
126  public void resetDirty() {
127    fetchedTime = fetchedTime | 1;
128  }
129
130  /** Reset next as a marker for {@link #isRemovedFromReplacementList()} */
131  public final void removedFromList() {
132    next = null;
133  }
134
135  /** Check that this entry is removed from the list, may be used in assertions. */
136  public boolean isRemovedFromReplacementList() {
137    return isStale () || next == null;
138  }
139
140  public E shortCircuit() {
141    return next = prev = (E) this;
142  }
143
144  public final boolean isVirgin() {
145    return
146      nextRefreshTime == VIRGIN_STATE ||
147      nextRefreshTime == FETCH_IN_PROGRESS_VIRGIN;
148  }
149
150  public final boolean isFetchNextTimeState() {
151    return nextRefreshTime == FETCH_NEXT_TIME_STATE;
152  }
153
154  /**
155   * The entry value was fetched and is valid, which means it can be
156   * returned by the cache. If a valid entry gets removed from the
157   * cache the data is still valid. This is because a concurrent get needs to
158   * return the data. There is also the chance that an entry is removed by eviction,
159   * or is never inserted to the cache, before the get returns it.
160   *
161   * <p/>Even if this is true, the data may be expired. Use hasFreshData() to
162   * make sure to get not expired data.
163   */
164  public final boolean isDataValidState() {
165    return nextRefreshTime >= FETCHED_STATE || nextRefreshTime < 0;
166  }
167
168  /**
169   * Starts long operation on entry. Pins the entry in the cache.
170   */
171  public void startFetch() {
172    if (isVirgin()) {
173      nextRefreshTime = FETCH_IN_PROGRESS_VIRGIN;
174    } else {
175      nextRefreshTime = FETCH_IN_PROGRESS_NON_VALID;
176    }
177  }
178
179  public void finishFetch(long _nextRefreshTime) {
180    synchronized (Entry.this) {
181      nextRefreshTime = _nextRefreshTime;
182      notifyAll();
183    }
184  }
185
186  /**
187   * If fetch is not stopped, abort and make entry invalid.
188   * This is a safety measure, since during entry processing an
189   * exceptions may happen. This can happen regularly e.g. if storage
190   * is set to read only and a cache put is made.
191   */
192  public void ensureFetchAbort(boolean _finished) {
193    if (_finished) {
194      return;
195    }
196    if (isFetchInProgress()) {
197      synchronized (Entry.this) {
198        if (isFetchInProgress()) {
199          nextRefreshTime = FETCH_ABORT;
200          notifyAll();
201        }
202      }
203    }
204  }
205
206  /**
207   * Entry is not allowed to be evicted
208   */
209  public boolean isPinned() {
210    return isFetchInProgress();
211  }
212
213  public boolean isFetchInProgress() {
214    return
215      nextRefreshTime == REFRESH_STATE ||
216      nextRefreshTime == LOADED_NON_VALID_AND_FETCH ||
217      nextRefreshTime == FETCH_IN_PROGRESS_VIRGIN ||
218      nextRefreshTime == LOADED_NON_VALID_AND_PUT ||
219      nextRefreshTime == FETCH_IN_PROGRESS_NON_VALID ||
220      nextRefreshTime == FETCH_IN_PROGRESS_VALID;
221  }
222
223  public void waitForFetch() {
224    if (!isFetchInProgress()) {
225      return;
226    }
227    try {
228      do {
229        wait();
230      } while (isFetchInProgress());
231    } catch (InterruptedException e) {
232      throw new CacheInternalError();
233    }
234  }
235
236  /**
237   * Returns true if the entry has a valid value and is fresh / not expired.
238   */
239  public final boolean hasFreshData() {
240    if (nextRefreshTime >= FETCHED_STATE) {
241      return true;
242    }
243    if (needsTimeCheck()) {
244      long now = System.currentTimeMillis();
245      return now < -nextRefreshTime;
246    }
247    return false;
248  }
249
250  /**
251   * Same as {@link #hasFreshData}, optimization if current time is known.
252   */
253  public final boolean hasFreshData(long now) {
254    if (nextRefreshTime >= FETCHED_STATE) {
255      return true;
256    }
257    if (needsTimeCheck()) {
258      return now < -nextRefreshTime;
259    }
260    return false;
261  }
262
263  public final boolean hasFreshData(long now, long _nextRefreshTime) {
264    if (_nextRefreshTime >= FETCHED_STATE) {
265      return true;
266    }
267    if (_nextRefreshTime < 0) {
268      return now < -_nextRefreshTime;
269    }
270    return false;
271  }
272
273  public boolean isLoadedNonValid() {
274    return nextRefreshTime == LOADED_NON_VALID;
275  }
276
277  public void setLoadedNonValidAndFetch() {
278    nextRefreshTime = LOADED_NON_VALID_AND_FETCH;
279  }
280
281  public boolean isLoadedNonValidAndFetch() {
282    return nextRefreshTime == LOADED_NON_VALID_AND_FETCH;
283  }
284
285  /** Entry is kept in the cache but has expired */
286  public void setExpiredState() {
287    nextRefreshTime = EXPIRED_STATE;
288  }
289
290  /**
291   * The entry expired, but still in the cache. This may happen if
292   * {@link BaseCache#hasKeepAfterExpired()} is true.
293   */
294  public boolean isExpiredState() {
295    return nextRefreshTime == EXPIRED_STATE;
296  }
297
298  public void setRemovedState() {
299    nextRefreshTime = REMOVED_STATE;
300  }
301
302  public boolean isRemovedState() {
303    return nextRefreshTime == REMOVED_STATE;
304  }
305
306  public void setGettingRefresh() {
307    nextRefreshTime = REFRESH_STATE;
308  }
309
310  public boolean isGettingRefresh() {
311    return nextRefreshTime == REFRESH_STATE;
312  }
313
314  public boolean isBeeingReput() {
315    return nextRefreshTime == REPUT_STATE;
316  }
317
318  public boolean needsTimeCheck() {
319    return nextRefreshTime < 0;
320  }
321
322  public boolean isStale() {
323    return STALE_MARKER_KEY == key;
324  }
325
326  public void setStale() {
327    key = (K) STALE_MARKER_KEY;
328  }
329
330  public boolean hasException() {
331    return value instanceof ExceptionWrapper;
332  }
333
334  public Throwable getException() {
335    if (value instanceof ExceptionWrapper) {
336      return ((ExceptionWrapper) value).getException();
337    }
338    return null;
339  }
340
341  public void setException(Throwable exception) {
342    value = (T) new ExceptionWrapper(exception);
343  }
344
345  public T getValue() {
346    if (value instanceof ExceptionWrapper) { return null; }
347    return value;
348  }
349
350
351  @Override
352  public void setValue(T v) {
353    value = v;
354  }
355
356  @Override
357  public K getKey() {
358    return key;
359  }
360
361  @Override
362  public long getLastModification() {
363    return fetchedTime >> 1;
364  }
365
366  /**
367   * Expiry time or 0.
368   */
369  public long getValueExpiryTime() {
370    if (nextRefreshTime < 0) {
371      return -nextRefreshTime;
372    } else if (nextRefreshTime > EXPIRY_TIME_MIN) {
373      return nextRefreshTime;
374    }
375    return 0;
376  }
377
378  /**
379   * Used for the storage interface.
380   *
381   * @see org.cache2k.storage.StorageEntry
382   */
383  @Override
384  public Object getValueOrException() {
385    return value;
386  }
387
388  /**
389   * Used for the storage interface.
390   *
391   * @see org.cache2k.storage.StorageEntry
392   */
393  @Override
394  public long getCreatedOrUpdated() {
395    return getLastModification();
396  }
397
398  /**
399   * Used for the storage interface.
400   *
401   * @see org.cache2k.storage.StorageEntry
402   * @deprectated Always returns 0, only to fulfill the {@link org.cache2k.storage.StorageEntry} interface
403   */
404  @Override
405  public long getEntryExpiryTime() {
406    return 0;
407  }
408
409  @Override
410  public String toString() {
411    return "Entry{" +
412      "createdOrUpdate=" + getCreatedOrUpdated() +
413      ", nextRefreshTime=" + nextRefreshTime +
414      ", valueExpiryTime=" + getValueExpiryTime() +
415      ", entryExpiryTime=" + getEntryExpiryTime() +
416      ", key=" + key +
417      ", mHC=" + hashCode +
418      ", value=" + value +
419      ", dirty=" + isDirty() +
420      '}';
421  }
422
423  /**
424   * Cache entries always have the object identity as equals method.
425   */
426  @Override
427  public final boolean equals(Object obj) {
428    return this == obj;
429  }
430
431  /* check entry states */
432  static {
433    Entry e = new Entry();
434    e.nextRefreshTime = FETCHED_STATE;
435    e.setGettingRefresh();
436    e = new Entry();
437    e.setLoadedNonValidAndFetch();
438    e.setExpiredState();
439  }
440
441  static class InitialValueInEntryNeverReturned extends Object { }
442
443  static class StaleMarker {
444    @Override
445    public boolean equals(Object o) { return false; }
446  }
447
448}