12bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch/*
203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * Copyright (C) 2011 The Android Open Source Project
32bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch *
42bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * Licensed under the Apache License, Version 2.0 (the "License");
52bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * you may not use this file except in compliance with the License.
62bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * You may obtain a copy of the License at
72bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch *
82bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch *      http://www.apache.org/licenses/LICENSE-2.0
92bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch *
102bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * Unless required by applicable law or agreed to in writing, software
112bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * distributed under the License is distributed on an "AS IS" BASIS,
122bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * See the License for the specific language governing permissions and
142bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch * limitations under the License.
152bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch */
162bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
172bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochpackage com.example.android.bitmapfun.util;
182bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
1903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.BufferedInputStream;
2003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.BufferedWriter;
2103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.Closeable;
2203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.EOFException;
232bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.io.File;
2403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.FileInputStream;
252bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.io.FileNotFoundException;
262bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.io.FileOutputStream;
2703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.FileWriter;
2803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.FilterOutputStream;
292bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.io.IOException;
3003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.InputStream;
3103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.InputStreamReader;
322bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.io.OutputStream;
3303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.OutputStreamWriter;
3403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.Reader;
3503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.StringWriter;
3603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.io.Writer;
3703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.lang.reflect.Array;
3803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.nio.charset.Charset;
3903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.ArrayList;
4003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.Arrays;
4103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.Iterator;
422bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.util.LinkedHashMap;
432bab0137e0af92c374d3efb2ed0e1d894981e015Adam Kochimport java.util.Map;
4403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.concurrent.Callable;
4503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.concurrent.ExecutorService;
4603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.concurrent.LinkedBlockingQueue;
4703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.concurrent.ThreadPoolExecutor;
4803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochimport java.util.concurrent.TimeUnit;
492bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
502bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch/**
5103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch ******************************************************************************
5203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * Taken from the JB source code, can be found in:
5303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
5403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * or direct link:
5503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
5603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch ******************************************************************************
5703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *
5803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * A cache that uses a bounded amount of space on a filesystem. Each cache
5903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * entry has a string key and a fixed number of values. Values are byte
6003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * sequences, accessible as streams or files. Each value must be between {@code
6103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * 0} and {@code Integer.MAX_VALUE} bytes in length.
6203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *
6303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * <p>The cache stores its data in a directory on the filesystem. This
6403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * directory must be exclusive to the cache; the cache may delete or overwrite
6503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * files from its directory. It is an error for multiple processes to use the
6603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * same cache directory at the same time.
6703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *
6803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * <p>This cache limits the number of bytes that it will store on the
6903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * filesystem. When the number of stored bytes exceeds the limit, the cache will
7003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * remove entries in the background until the limit is satisfied. The limit is
7103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * not strict: the cache may temporarily exceed it while waiting for files to be
7203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * deleted. The limit does not include filesystem overhead or the cache
7303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * journal so space-sensitive applications should set a conservative limit.
7403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *
7503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * <p>Clients call {@link #edit} to create or update the values of an entry. An
7603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * entry may have only one editor at one time; if a value is not available to be
7703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * edited then {@link #edit} will return null.
7803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * <ul>
7903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *     <li>When an entry is being <strong>created</strong> it is necessary to
8003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *         supply a full set of values; the empty value should be used as a
8103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *         placeholder if necessary.
8203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *     <li>When an entry is being <strong>edited</strong>, it is not necessary
8303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *         to supply data for every value; values default to their previous
8403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *         value.
8503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * </ul>
8603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
8703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * or {@link Editor#abort}. Committing is atomic: a read observes the full set
8803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * of values as they were before or after the commit, but never a mix of values.
8903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *
9003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
9103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * observe the value at the time that {@link #get} was called. Updates and
9203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * removals after the call do not impact ongoing reads.
9303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch *
9403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * <p>This class is tolerant of some I/O errors. If files are missing from the
9503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * filesystem, the corresponding entries will be dropped from the cache. If
9603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * an error occurs while writing a cache value, the edit will fail silently.
9703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * Callers should handle other problems by catching {@code IOException} and
9803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch * responding appropriately.
992bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch */
10003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Kochpublic final class DiskLruCache implements Closeable {
10103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    static final String JOURNAL_FILE = "journal";
10203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    static final String JOURNAL_FILE_TMP = "journal.tmp";
10303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    static final String MAGIC = "libcore.io.DiskLruCache";
10403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    static final String VERSION_1 = "1";
10503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    static final long ANY_SEQUENCE_NUMBER = -1;
10603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static final String CLEAN = "CLEAN";
10703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static final String DIRTY = "DIRTY";
10803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static final String REMOVE = "REMOVE";
10903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static final String READ = "READ";
11003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
11103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static final Charset UTF_8 = Charset.forName("UTF-8");
11203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static final int IO_BUFFER_SIZE = 8 * 1024;
11303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
11403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /*
11503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * This cache uses a journal file named "journal". A typical journal file
11603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * looks like this:
11703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     libcore.io.DiskLruCache
11803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     1
11903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     100
12003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     2
12103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *
12203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
12303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
12403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
12503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
12603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
12703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
12803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     READ 335c4c6028171cfddfbaae1a9c313c52
12903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
13003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *
13103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * The first five lines of the journal form its header. They are the
13203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * constant string "libcore.io.DiskLruCache", the disk cache's version,
13303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * the application's version, the value count, and a blank line.
13403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *
13503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Each of the subsequent lines in the file is a record of the state of a
13603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * cache entry. Each line contains space-separated values: a state, a key,
13703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * and optional state-specific values.
13803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *   o DIRTY lines track that an entry is actively being created or updated.
13903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
14003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
14103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     temporary files may need to be deleted.
14203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *   o CLEAN lines track a cache entry that has been successfully published
14303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     and may be read. A publish line is followed by the lengths of each of
14403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     its values.
14503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *   o READ lines track accesses for LRU.
14603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *   o REMOVE lines track entries that have been deleted.
14703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *
14803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * The journal file is appended to as cache operations occur. The journal may
14903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * occasionally be compacted by dropping redundant lines. A temporary file named
15003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * "journal.tmp" will be used during compaction; that file should be deleted if
15103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * it exists when the cache is opened.
15203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
15303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
15403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final File directory;
15503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final File journalFile;
15603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final File journalFileTmp;
15703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final int appVersion;
15803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final long maxSize;
15903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final int valueCount;
16003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private long size = 0;
16103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private Writer journalWriter;
16203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final LinkedHashMap<String, Entry> lruEntries
16303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            = new LinkedHashMap<String, Entry>(0, 0.75f, true);
16403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private int redundantOpCount;
1652bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
1662bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
16703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * To differentiate between old and current snapshots, each entry is given
16803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * a sequence number each time an edit is committed. A snapshot is stale if
16903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * its sequence number is not equal to its entry's sequence number.
1702bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
17103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private long nextSequenceNumber = 0;
17203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
17303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /* From java.util.Arrays */
17403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    @SuppressWarnings("unchecked")
17503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static <T> T[] copyOfRange(T[] original, int start, int end) {
17603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        final int originalLength = original.length; // For exception priority compatibility.
17703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (start > end) {
17803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalArgumentException();
1792bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
18003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (start < 0 || start > originalLength) {
18103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new ArrayIndexOutOfBoundsException();
18203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
18303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        final int resultLength = end - start;
18403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        final int copyLength = Math.min(resultLength, originalLength - start);
18503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        final T[] result = (T[]) Array
18603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                .newInstance(original.getClass().getComponentType(), resultLength);
18703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        System.arraycopy(original, start, result, 0, copyLength);
18803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return result;
18903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
1902bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
1912bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
19203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns the remainder of 'reader' as a string, closing it when done.
1932bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
19403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public static String readFully(Reader reader) throws IOException {
19503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        try {
19603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            StringWriter writer = new StringWriter();
19703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            char[] buffer = new char[1024];
19803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            int count;
19903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            while ((count = reader.read(buffer)) != -1) {
20003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                writer.write(buffer, 0, count);
20103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
20203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return writer.toString();
20303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } finally {
20403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            reader.close();
2052bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
20603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
20703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
20803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /**
20903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns the ASCII characters up to but not including the next "\r\n", or
21003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * "\n".
21103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *
21203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @throws java.io.EOFException if the stream is exhausted before the next newline
21303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     *     character.
21403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
21503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public static String readAsciiLine(InputStream in) throws IOException {
21603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        // TODO: support UTF-8 here instead
2172bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
21803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        StringBuilder result = new StringBuilder(80);
21903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        while (true) {
22003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            int c = in.read();
22103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (c == -1) {
22203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw new EOFException();
22303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } else if (c == '\n') {
22403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                break;
22503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
22603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
22703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            result.append((char) c);
2282bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
22903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        int length = result.length();
23003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (length > 0 && result.charAt(length - 1) == '\r') {
23103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            result.setLength(length - 1);
23203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
23303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return result.toString();
23403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
2352bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
23603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /**
23703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
23803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
23903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public static void closeQuietly(Closeable closeable) {
24003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (closeable != null) {
24103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            try {
24203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                closeable.close();
24303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } catch (RuntimeException rethrown) {
24403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw rethrown;
24503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } catch (Exception ignored) {
24603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
24703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
2482bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
2492bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
2502bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
25103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Recursively delete everything in {@code dir}.
2522bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
25303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    // TODO: this should specify paths as Strings rather than as Files
25403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public static void deleteContents(File dir) throws IOException {
25503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        File[] files = dir.listFiles();
25603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (files == null) {
25703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalArgumentException("not a directory: " + dir);
25803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
25903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        for (File file : files) {
26003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (file.isDirectory()) {
26103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                deleteContents(file);
26203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
26303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (!file.delete()) {
26403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw new IOException("failed to delete file: " + file);
26503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
26603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
26703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
26803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
26903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /** This cache uses a single background thread to evict entries. */
27003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
27103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
27203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final Callable<Void> cleanupCallable = new Callable<Void>() {
27303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        @Override public Void call() throws Exception {
27403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            synchronized (DiskLruCache.this) {
27503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (journalWriter == null) {
27603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    return null; // closed
27703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
27803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                trimToSize();
27903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (journalRebuildRequired()) {
28003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    rebuildJournal();
28103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    redundantOpCount = 0;
28203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
28303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
28403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return null;
28503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
28603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    };
28703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
28803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
28903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        this.directory = directory;
29003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        this.appVersion = appVersion;
29103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        this.journalFile = new File(directory, JOURNAL_FILE);
29203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
29303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        this.valueCount = valueCount;
29403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        this.maxSize = maxSize;
2952bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
2962bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
2972bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
29803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Opens the cache in {@code directory}, creating a cache if none exists
29903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * there.
3002bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     *
30103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @param directory a writable directory
30203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @param appVersion
30303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @param valueCount the number of values per cache entry. Must be positive.
30403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @param maxSize the maximum number of bytes this cache should use to store
30503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @throws IOException if reading or writing the cache directory fails
3062bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
30703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
30803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throws IOException {
30903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (maxSize <= 0) {
31003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalArgumentException("maxSize <= 0");
31103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
31203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (valueCount <= 0) {
31303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalArgumentException("valueCount <= 0");
31403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
31503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
31603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        // prefer to pick up where we left off
31703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
31803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (cache.journalFile.exists()) {
31903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            try {
32003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                cache.readJournal();
32103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                cache.processJournal();
32203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
32303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                        IO_BUFFER_SIZE);
32403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                return cache;
32503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } catch (IOException journalIsCorrupt) {
32603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//                System.logW("DiskLruCache " + directory + " is corrupt: "
32703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//                        + journalIsCorrupt.getMessage() + ", removing");
32803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                cache.delete();
32903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
33003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
33103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
33203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        // create a new empty cache
33303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        directory.mkdirs();
33403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
33503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        cache.rebuildJournal();
33603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return cache;
33703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
33803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
33903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private void readJournal() throws IOException {
34003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
34103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        try {
34203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            String magic = readAsciiLine(in);
34303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            String version = readAsciiLine(in);
34403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            String appVersionString = readAsciiLine(in);
34503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            String valueCountString = readAsciiLine(in);
34603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            String blank = readAsciiLine(in);
34703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (!MAGIC.equals(magic)
34803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    || !VERSION_1.equals(version)
34903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    || !Integer.toString(appVersion).equals(appVersionString)
35003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    || !Integer.toString(valueCount).equals(valueCountString)
35103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    || !"".equals(blank)) {
35203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw new IOException("unexpected journal header: ["
35303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                        + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
35403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
35503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
35603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            while (true) {
3572bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch                try {
35803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    readJournalLine(readAsciiLine(in));
35903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                } catch (EOFException endOfJournal) {
36003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    break;
3612bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch                }
3622bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch            }
36303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } finally {
36403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            closeQuietly(in);
3652bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
3662bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
3672bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
36803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private void readJournalLine(String line) throws IOException {
36903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        String[] parts = line.split(" ");
37003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (parts.length < 2) {
37103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IOException("unexpected journal line: " + line);
37203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
37303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
37403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        String key = parts[1];
37503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (parts[0].equals(REMOVE) && parts.length == 2) {
37603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            lruEntries.remove(key);
37703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return;
37803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
37903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
38003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Entry entry = lruEntries.get(key);
38103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (entry == null) {
38203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry = new Entry(key);
38303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            lruEntries.put(key, entry);
38403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
38503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
38603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
38703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry.readable = true;
38803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry.currentEditor = null;
38903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry.setLengths(copyOfRange(parts, 2, parts.length));
39003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } else if (parts[0].equals(DIRTY) && parts.length == 2) {
39103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry.currentEditor = new Editor(entry);
39203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } else if (parts[0].equals(READ) && parts.length == 2) {
39303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            // this work was already done by calling lruEntries.get()
39403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } else {
39503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IOException("unexpected journal line: " + line);
39603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
3972bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
3982bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
3992bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
40003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Computes the initial size and collects garbage as a part of opening the
40103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * cache. Dirty entries are assumed to be inconsistent and will be deleted.
4022bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
40303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private void processJournal() throws IOException {
40403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        deleteIfExists(journalFileTmp);
40503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
40603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            Entry entry = i.next();
40703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (entry.currentEditor == null) {
40803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                for (int t = 0; t < valueCount; t++) {
40903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    size += entry.lengths[t];
41003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
41103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } else {
41203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                entry.currentEditor = null;
41303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                for (int t = 0; t < valueCount; t++) {
41403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    deleteIfExists(entry.getCleanFile(t));
41503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    deleteIfExists(entry.getDirtyFile(t));
41603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
41703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                i.remove();
4182bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch            }
4192bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
4202bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
4212bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
4222bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
42303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Creates a new journal that omits redundant information. This replaces the
42403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * current journal if it exists.
4252bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
42603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private synchronized void rebuildJournal() throws IOException {
42703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (journalWriter != null) {
42803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            journalWriter.close();
42903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
43003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
43103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
43203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write(MAGIC);
43303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write("\n");
43403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write(VERSION_1);
43503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write("\n");
43603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write(Integer.toString(appVersion));
43703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write("\n");
43803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write(Integer.toString(valueCount));
43903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write("\n");
44003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.write("\n");
44103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
44203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        for (Entry entry : lruEntries.values()) {
44303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (entry.currentEditor != null) {
44403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                writer.write(DIRTY + ' ' + entry.key + '\n');
4452bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch            } else {
44603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
4472bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch            }
44803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
44903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
45003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        writer.close();
45103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalFileTmp.renameTo(journalFile);
45203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
45303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
45403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
45503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static void deleteIfExists(File file) throws IOException {
45603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//        try {
45703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//            Libcore.os.remove(file.getPath());
45803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//        } catch (ErrnoException errnoException) {
45903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//            if (errnoException.errno != OsConstants.ENOENT) {
46003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//                throw errnoException.rethrowAsIOException();
46103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//            }
46203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//        }
46303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (file.exists() && !file.delete()) {
46403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IOException();
4652bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
4662bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
4672bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
4682bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
46903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns a snapshot of the entry named {@code key}, or null if it doesn't
47003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * exist is not currently readable. If a value is returned, it is moved to
47103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * the head of the LRU queue.
4722bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
47303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public synchronized Snapshot get(String key) throws IOException {
47403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        checkNotClosed();
47503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        validateKey(key);
47603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Entry entry = lruEntries.get(key);
47703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (entry == null) {
47803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return null;
47903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
48003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
48103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (!entry.readable) {
48203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return null;
48303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
48403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
48503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /*
48603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Open all streams eagerly to guarantee that we see a single published
48703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * snapshot. If we opened streams lazily then the streams could come
48803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * from different edits.
48903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
49003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        InputStream[] ins = new InputStream[valueCount];
49103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        try {
49203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            for (int i = 0; i < valueCount; i++) {
49303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                ins[i] = new FileInputStream(entry.getCleanFile(i));
49403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
49503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } catch (FileNotFoundException e) {
49603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            // a file must have been deleted manually!
49703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return null;
4982bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
4992bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
50003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        redundantOpCount++;
50103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter.append(READ + ' ' + key + '\n');
50203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (journalRebuildRequired()) {
50303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            executorService.submit(cleanupCallable);
5042bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
50503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
50603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return new Snapshot(key, entry.sequenceNumber, ins);
5072bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
5082bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
5092bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
51003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns an editor for the entry named {@code key}, or null if another
51103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * edit is in progress.
5122bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
51303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public Editor edit(String key) throws IOException {
51403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return edit(key, ANY_SEQUENCE_NUMBER);
51503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
51603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
51703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
51803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        checkNotClosed();
51903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        validateKey(key);
52003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Entry entry = lruEntries.get(key);
52103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
52203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
52303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return null; // snapshot is stale
52403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
52503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (entry == null) {
52603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry = new Entry(key);
52703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            lruEntries.put(key, entry);
52803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } else if (entry.currentEditor != null) {
52903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return null; // another edit is in progress
53003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
53103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
53203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Editor editor = new Editor(entry);
53303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        entry.currentEditor = editor;
53403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
53503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        // flush the journal before creating files to prevent file leaks
53603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter.write(DIRTY + ' ' + key + '\n');
53703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter.flush();
53803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return editor;
5392bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
5402bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
5412bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
54203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns the directory where this cache stores its data.
5432bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
54403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public File getDirectory() {
54503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return directory;
5462bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
5472bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
5482bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
54903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns the maximum number of bytes that this cache should use to store
55003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * its data.
5512bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
55203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public long maxSize() {
55303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return maxSize;
55403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
55503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
55603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /**
55703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns the number of bytes currently being used to store the values in
55803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * this cache. This may be greater than the max size if a background
55903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * deletion is pending.
56003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
56103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public synchronized long size() {
56203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return size;
56303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
56403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
56503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
56603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Entry entry = editor.entry;
56703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (entry.currentEditor != editor) {
56803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalStateException();
56903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
57003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
57103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        // if this edit is creating the entry for the first time, every index must have a value
57203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (success && !entry.readable) {
57303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            for (int i = 0; i < valueCount; i++) {
57403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (!entry.getDirtyFile(i).exists()) {
57503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    editor.abort();
57603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    throw new IllegalStateException("edit didn't create file " + i);
57703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
57803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
57903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
58003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
58103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        for (int i = 0; i < valueCount; i++) {
58203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            File dirty = entry.getDirtyFile(i);
58303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (success) {
58403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (dirty.exists()) {
58503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    File clean = entry.getCleanFile(i);
58603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    dirty.renameTo(clean);
58703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    long oldLength = entry.lengths[i];
58803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    long newLength = clean.length();
58903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    entry.lengths[i] = newLength;
59003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    size = size - oldLength + newLength;
59103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
59203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } else {
59303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                deleteIfExists(dirty);
59403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
59503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
59603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
59703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        redundantOpCount++;
59803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        entry.currentEditor = null;
59903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (entry.readable | success) {
60003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry.readable = true;
60103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
60203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (success) {
60303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                entry.sequenceNumber = nextSequenceNumber++;
60403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
60503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        } else {
60603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            lruEntries.remove(entry.key);
60703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            journalWriter.write(REMOVE + ' ' + entry.key + '\n');
60803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
60903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
61003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (size > maxSize || journalRebuildRequired()) {
61103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            executorService.submit(cleanupCallable);
6122bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
6132bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
6142bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
6152bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
61603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * We only rebuild the journal when it will halve the size of the journal
61703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * and eliminate at least 2000 ops.
61803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
61903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private boolean journalRebuildRequired() {
62003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
62103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
62203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                && redundantOpCount >= lruEntries.size();
62303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
62403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
62503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /**
62603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Drops the entry for {@code key} if it exists and can be removed. Entries
62703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * actively being edited cannot be removed.
6282bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     *
62903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * @return true if an entry was removed.
6302bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
63103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public synchronized boolean remove(String key) throws IOException {
63203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        checkNotClosed();
63303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        validateKey(key);
63403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        Entry entry = lruEntries.get(key);
63503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (entry == null || entry.currentEditor != null) {
63603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return false;
63703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
63803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
63903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        for (int i = 0; i < valueCount; i++) {
64003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            File file = entry.getCleanFile(i);
64103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (!file.delete()) {
64203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw new IOException("failed to delete " + file);
64303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
64403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            size -= entry.lengths[i];
64503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            entry.lengths[i] = 0;
64603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
6472bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
64803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        redundantOpCount++;
64903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter.append(REMOVE + ' ' + key + '\n');
65003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        lruEntries.remove(key);
6512bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
65203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (journalRebuildRequired()) {
65303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            executorService.submit(cleanupCallable);
65403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
65503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
65603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return true;
6572bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
6582bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
6592bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
66003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Returns true if this cache has been closed.
6612bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
66203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public boolean isClosed() {
66303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return journalWriter == null;
66403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
66503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
66603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private void checkNotClosed() {
66703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (journalWriter == null) {
66803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalStateException("cache is closed");
6692bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
67003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
6712bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
67203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /**
67303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Force buffered operations to the filesystem.
67403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
67503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public synchronized void flush() throws IOException {
67603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        checkNotClosed();
67703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        trimToSize();
67803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter.flush();
6792bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
6802bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
6812bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
68203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Closes this cache. Stored values will remain on the filesystem.
68303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     */
68403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public synchronized void close() throws IOException {
68503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (journalWriter == null) {
68603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return; // already closed
68703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
68803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
68903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (entry.currentEditor != null) {
69003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                entry.currentEditor.abort();
69103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
69203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
69303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        trimToSize();
69403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter.close();
69503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        journalWriter = null;
69603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
69703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
69803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private void trimToSize() throws IOException {
69903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        while (size > maxSize) {
70003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch//            Map.Entry<String, Entry> toEvict = lruEntries.eldest();
70103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
70203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            remove(toEvict.getKey());
70303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
70403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
70503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
70603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    /**
70703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Closes the cache and deletes all of its stored values. This will delete
70803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * all files in the cache directory including files that weren't created by
70903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * the cache.
7102bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
71103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public void delete() throws IOException {
71203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        close();
71303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        deleteContents(directory);
71403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
71503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
71603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private void validateKey(String key) {
71703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
71803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IllegalArgumentException(
71903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    "keys must not contain spaces or newlines: \"" + key + "\"");
72003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
72103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
72203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
72303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private static String inputStreamToString(InputStream in) throws IOException {
72403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        return readFully(new InputStreamReader(in, UTF_8));
7252bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
7262bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
7272bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
72803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * A snapshot of the values for an entry.
7292bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
73003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public final class Snapshot implements Closeable {
73103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private final String key;
73203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private final long sequenceNumber;
73303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private final InputStream[] ins;
73403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
73503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
73603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            this.key = key;
73703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            this.sequenceNumber = sequenceNumber;
73803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            this.ins = ins;
73903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
74003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
74103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
74203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Returns an editor for this snapshot's entry, or null if either the
74303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * entry has changed since this snapshot was created or if another edit
74403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * is in progress.
74503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
74603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public Editor edit() throws IOException {
74703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return DiskLruCache.this.edit(key, sequenceNumber);
74803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
74903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
75003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
75103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Returns the unbuffered stream with the value for {@code index}.
75203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
75303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public InputStream getInputStream(int index) {
75403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return ins[index];
75503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
75603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
75703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
75803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Returns the string value for {@code index}.
75903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
76003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public String getString(int index) throws IOException {
76103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return inputStreamToString(getInputStream(index));
76203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
76303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
76403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        @Override public void close() {
76503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            for (InputStream in : ins) {
76603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                closeQuietly(in);
76703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
76803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
7692bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
7702bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
7712bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    /**
77203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch     * Edits the values for an entry.
7732bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch     */
77403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    public final class Editor {
77503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private final Entry entry;
77603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private boolean hasErrors;
7772bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch
77803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private Editor(Entry entry) {
77903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            this.entry = entry;
78003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
78103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
78203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
78303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Returns an unbuffered input stream to read the last committed value,
78403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * or null if no value has been committed.
78503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
78603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public InputStream newInputStream(int index) throws IOException {
78703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            synchronized (DiskLruCache.this) {
78803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (entry.currentEditor != this) {
78903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    throw new IllegalStateException();
79003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
79103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (!entry.readable) {
79203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    return null;
79303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
79403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                return new FileInputStream(entry.getCleanFile(index));
79503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
79603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
79703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
79803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
79903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Returns the last committed value as a string, or null if no value
80003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * has been committed.
80103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
80203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public String getString(int index) throws IOException {
80303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            InputStream in = newInputStream(index);
80403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return in != null ? inputStreamToString(in) : null;
80503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
80603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
80703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
80803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Returns a new unbuffered output stream to write the value at
80903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * {@code index}. If the underlying output stream encounters errors
81003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * when writing to the filesystem, this edit will be aborted when
81103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * {@link #commit} is called. The returned output stream does not throw
81203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * IOExceptions.
81303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
81403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public OutputStream newOutputStream(int index) throws IOException {
81503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            synchronized (DiskLruCache.this) {
81603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                if (entry.currentEditor != this) {
81703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    throw new IllegalStateException();
81803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
81903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
82003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
82103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
82203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
82303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
82403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Sets the value at {@code index} to {@code value}.
82503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
82603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public void set(int index, String value) throws IOException {
82703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            Writer writer = null;
82803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            try {
82903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
83003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                writer.write(value);
83103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } finally {
83203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                closeQuietly(writer);
83303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
83403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
83503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
83603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
83703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Commits this edit so it is visible to readers.  This releases the
83803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * edit lock so another edit may be started on the same key.
83903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
84003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public void commit() throws IOException {
84103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (hasErrors) {
84203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                completeEdit(this, false);
84303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                remove(entry.key); // the previous entry is stale
84403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } else {
84503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                completeEdit(this, true);
84603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
84703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
84803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
84903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
85003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Aborts this edit. This releases the edit lock so another edit may be
85103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * started on the same key.
85203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
85303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public void abort() throws IOException {
85403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            completeEdit(this, false);
85503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
85603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
85703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private class FaultHidingOutputStream extends FilterOutputStream {
85803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            private FaultHidingOutputStream(OutputStream out) {
85903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                super(out);
86003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
86103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
86203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            @Override public void write(int oneByte) {
86303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                try {
86403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    out.write(oneByte);
86503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                } catch (IOException e) {
86603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    hasErrors = true;
86703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
86803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
86903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
87003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            @Override public void write(byte[] buffer, int offset, int length) {
87103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                try {
87203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    out.write(buffer, offset, length);
87303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                } catch (IOException e) {
87403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    hasErrors = true;
87503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
87603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
87703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
87803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            @Override public void close() {
87903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                try {
88003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    out.close();
88103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                } catch (IOException e) {
88203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    hasErrors = true;
88303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
88403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
88503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
88603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            @Override public void flush() {
88703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                try {
88803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    out.flush();
88903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                } catch (IOException e) {
89003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    hasErrors = true;
89103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
8922bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch            }
8932bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch        }
8942bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch    }
89503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
89603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    private final class Entry {
89703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private final String key;
89803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
89903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /** Lengths of this entry's files. */
90003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private final long[] lengths;
90103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
90203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /** True if this entry has ever been published */
90303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private boolean readable;
90403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
90503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /** The ongoing edit or null if this entry is not being edited. */
90603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private Editor currentEditor;
90703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
90803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /** The sequence number of the most recently committed edit to this entry. */
90903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private long sequenceNumber;
91003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
91103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private Entry(String key) {
91203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            this.key = key;
91303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            this.lengths = new long[valueCount];
91403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
91503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
91603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public String getLengths() throws IOException {
91703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            StringBuilder result = new StringBuilder();
91803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            for (long size : lengths) {
91903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                result.append(' ').append(size);
92003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
92103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return result.toString();
92203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
92303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
92403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        /**
92503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         * Set lengths using decimal numbers like "10123".
92603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch         */
92703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private void setLengths(String[] strings) throws IOException {
92803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            if (strings.length != valueCount) {
92903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw invalidLengths(strings);
93003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
93103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
93203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            try {
93303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                for (int i = 0; i < strings.length; i++) {
93403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                    lengths[i] = Long.parseLong(strings[i]);
93503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                }
93603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            } catch (NumberFormatException e) {
93703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch                throw invalidLengths(strings);
93803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            }
93903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
94003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
94103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        private IOException invalidLengths(String[] strings) throws IOException {
94203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            throw new IOException("unexpected journal line: " + Arrays.toString(strings));
94303ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
94403ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
94503ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public File getCleanFile(int i) {
94603ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return new File(directory, key + "." + i);
94703ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
94803ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch
94903ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        public File getDirtyFile(int i) {
95003ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch            return new File(directory, key + "." + i + ".tmp");
95103ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch        }
95203ceb37f79e31626aaac5318b3143a2cfafaea42Adam Koch    }
9532bab0137e0af92c374d3efb2ed0e1d894981e015Adam Koch}
954