Please note that the contents of this offline web site may be out of date. To access the most recent documentation visit the online version .
Note that links that point to online resources are green in color and will open in a new window.
We would love it if you could give us feedback about this material by filling this form (You have to be online to fill it)
DisplayingBitmaps / src / com.example.android.displayingbitmaps / util /

DiskLruCache.java

       
        1
       
       
        /*
       
       
        2
       
       
        * Copyright (C) 2011 The Android Open Source Project
       
       
        3
       
       
        *
       
       
        4
       
       
        * Licensed under the Apache License, Version 2.0 (the "License");
       
       
        5
       
       
        * you may not use this file except in compliance with the License.
       
       
        6
       
       
        * You may obtain a copy of the License at
       
       
        7
       
       
        *
       
       
        8
       
       
        *      http://www.apache.org/licenses/LICENSE-2.0
       
       
        9
       
       
        *
       
       
        10
       
       
        * Unless required by applicable law or agreed to in writing, software
       
       
        11
       
       
        * distributed under the License is distributed on an "AS IS" BASIS,
       
       
        12
       
       
        * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
       
        13
       
       
        * See the License for the specific language governing permissions and
       
       
        14
       
       
        * limitations under the License.
       
       
        15
       
       
        */
       
       
        16
       
       
       
       
        17
       
       
        package com.example.android.displayingbitmaps.util;
       
       
        18
       
       
       
       
        19
       
       
        import java.io.BufferedInputStream;
       
       
        20
       
       
        import java.io.BufferedWriter;
       
       
        21
       
       
        import java.io.Closeable;
       
       
        22
       
       
        import java.io.EOFException;
       
       
        23
       
       
        import java.io.File;
       
       
        24
       
       
        import java.io.FileInputStream;
       
       
        25
       
       
        import java.io.FileNotFoundException;
       
       
        26
       
       
        import java.io.FileOutputStream;
       
       
        27
       
       
        import java.io.FileWriter;
       
       
        28
       
       
        import java.io.FilterOutputStream;
       
       
        29
       
       
        import java.io.IOException;
       
       
        30
       
       
        import java.io.InputStream;
       
       
        31
       
       
        import java.io.InputStreamReader;
       
       
        32
       
       
        import java.io.OutputStream;
       
       
        33
       
       
        import java.io.OutputStreamWriter;
       
       
        34
       
       
        import java.io.Reader;
       
       
        35
       
       
        import java.io.StringWriter;
       
       
        36
       
       
        import java.io.Writer;
       
       
        37
       
       
        import java.lang.reflect.Array;
       
       
        38
       
       
        import java.nio.charset.Charset;
       
       
        39
       
       
        import java.util.ArrayList;
       
       
        40
       
       
        import java.util.Arrays;
       
       
        41
       
       
        import java.util.Iterator;
       
       
        42
       
       
        import java.util.LinkedHashMap;
       
       
        43
       
       
        import java.util.Map;
       
       
        44
       
       
        import java.util.concurrent.Callable;
       
       
        45
       
       
        import java.util.concurrent.ExecutorService;
       
       
        46
       
       
        import java.util.concurrent.LinkedBlockingQueue;
       
       
        47
       
       
        import java.util.concurrent.ThreadPoolExecutor;
       
       
        48
       
       
        import java.util.concurrent.TimeUnit;
       
       
        49
       
       
       
       
        50
       
       
        /**
       
       
        51
       
       
        ******************************************************************************
       
       
        52
       
       
        * Taken from the JB source code, can be found in:
       
       
        53
       
       
        * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
       
       
        54
       
       
        * or direct link:
       
       
        55
       
       
        * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
       
       
        56
       
       
        ******************************************************************************
       
       
        57
       
       
        *
       
       
        58
       
       
        * A cache that uses a bounded amount of space on a filesystem. Each cache
       
       
        59
       
       
        * entry has a string key and a fixed number of values. Values are byte
       
       
        60
       
       
        * sequences, accessible as streams or files. Each value must be between {@code
       
       
        61
       
       
        * 0} and {@code Integer.MAX_VALUE} bytes in length.
       
       
        62
       
       
        *
       
       
        63
       
       
        * <p>The cache stores its data in a directory on the filesystem. This
       
       
        64
       
       
        * directory must be exclusive to the cache; the cache may delete or overwrite
       
       
        65
       
       
        * files from its directory. It is an error for multiple processes to use the
       
       
        66
       
       
        * same cache directory at the same time.
       
       
        67
       
       
        *
       
       
        68
       
       
        * <p>This cache limits the number of bytes that it will store on the
       
       
        69
       
       
        * filesystem. When the number of stored bytes exceeds the limit, the cache will
       
       
        70
       
       
        * remove entries in the background until the limit is satisfied. The limit is
       
       
        71
       
       
        * not strict: the cache may temporarily exceed it while waiting for files to be
       
       
        72
       
       
        * deleted. The limit does not include filesystem overhead or the cache
       
       
        73
       
       
        * journal so space-sensitive applications should set a conservative limit.
       
       
        74
       
       
        *
       
       
        75
       
       
        * <p>Clients call {@link #edit} to create or update the values of an entry. An
       
       
        76
       
       
        * entry may have only one editor at one time; if a value is not available to be
       
       
        77
       
       
        * edited then {@link #edit} will return null.
       
       
        78
       
       
        * <ul>
       
       
        79
       
       
        *     <li>When an entry is being <strong>created</strong> it is necessary to
       
       
        80
       
       
        *         supply a full set of values; the empty value should be used as a
       
       
        81
       
       
        *         placeholder if necessary.
       
       
        82
       
       
        *     <li>When an entry is being <strong>edited</strong>, it is not necessary
       
       
        83
       
       
        *         to supply data for every value; values default to their previous
       
       
        84
       
       
        *         value.
       
       
        85
       
       
        * </ul>
       
       
        86
       
       
        * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
       
       
        87
       
       
        * or {@link Editor#abort}. Committing is atomic: a read observes the full set
       
       
        88
       
       
        * of values as they were before or after the commit, but never a mix of values.
       
       
        89
       
       
        *
       
       
        90
       
       
        * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
       
       
        91
       
       
        * observe the value at the time that {@link #get} was called. Updates and
       
       
        92
       
       
        * removals after the call do not impact ongoing reads.
       
       
        93
       
       
        *
       
       
        94
       
       
        * <p>This class is tolerant of some I/O errors. If files are missing from the
       
       
        95
       
       
        * filesystem, the corresponding entries will be dropped from the cache. If
       
       
        96
       
       
        * an error occurs while writing a cache value, the edit will fail silently.
       
       
        97
       
       
        * Callers should handle other problems by catching {@code IOException} and
       
       
        98
       
       
        * responding appropriately.
       
       
        99
       
       
        */
       
       
        100
       
       
        public final class DiskLruCache implements Closeable {
       
       
        101
       
       
        static final String JOURNAL_FILE = "journal";
       
       
        102
       
       
        static final String JOURNAL_FILE_TMP = "journal.tmp";
       
       
        103
       
       
        static final String MAGIC = "libcore.io.DiskLruCache";
       
       
        104
       
       
        static final String VERSION_1 = "1";
       
       
        105
       
       
        static final long ANY_SEQUENCE_NUMBER = -1;
       
       
        106
       
       
        private static final String CLEAN = "CLEAN";
       
       
        107
       
       
        private static final String DIRTY = "DIRTY";
       
       
        108
       
       
        private static final String REMOVE = "REMOVE";
       
       
        109
       
       
        private static final String READ = "READ";
       
       
        110
       
       
       
       
        111
       
       
        private static final Charset UTF_8 = Charset.forName("UTF-8");
       
       
        112
       
       
        private static final int IO_BUFFER_SIZE = 8 * 1024;
       
       
        113
       
       
       
       
        114
       
       
        /*
       
       
        115
       
       
        * This cache uses a journal file named "journal". A typical journal file
       
       
        116
       
       
        * looks like this:
       
       
        117
       
       
        *     libcore.io.DiskLruCache
       
       
        118
       
       
        *     1
       
       
        119
       
       
        *     100
       
       
        120
       
       
        *     2
       
       
        121
       
       
        *
       
       
        122
       
       
        *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
       
       
        123
       
       
        *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
       
       
        124
       
       
        *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
       
       
        125
       
       
        *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
       
       
        126
       
       
        *     DIRTY 1ab96a171faeeee38496d8b330771a7a
       
       
        127
       
       
        *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
       
       
        128
       
       
        *     READ 335c4c6028171cfddfbaae1a9c313c52
       
       
        129
       
       
        *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
       
       
        130
       
       
        *
       
       
        131
       
       
        * The first five lines of the journal form its header. They are the
       
       
        132
       
       
        * constant string "libcore.io.DiskLruCache", the disk cache's version,
       
       
        133
       
       
        * the application's version, the value count, and a blank line.
       
       
        134
       
       
        *
       
       
        135
       
       
        * Each of the subsequent lines in the file is a record of the state of a
       
       
        136
       
       
        * cache entry. Each line contains space-separated values: a state, a key,
       
       
        137
       
       
        * and optional state-specific values.
       
       
        138
       
       
        *   o DIRTY lines track that an entry is actively being created or updated.
       
       
        139
       
       
        *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
       
       
        140
       
       
        *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
       
       
        141
       
       
        *     temporary files may need to be deleted.
       
       
        142
       
       
        *   o CLEAN lines track a cache entry that has been successfully published
       
       
        143
       
       
        *     and may be read. A publish line is followed by the lengths of each of
       
       
        144
       
       
        *     its values.
       
       
        145
       
       
        *   o READ lines track accesses for LRU.
       
       
        146
       
       
        *   o REMOVE lines track entries that have been deleted.
       
       
        147
       
       
        *
       
       
        148
       
       
        * The journal file is appended to as cache operations occur. The journal may
       
       
        149
       
       
        * occasionally be compacted by dropping redundant lines. A temporary file named
       
       
        150
       
       
        * "journal.tmp" will be used during compaction; that file should be deleted if
       
       
        151
       
       
        * it exists when the cache is opened.
       
       
        152
       
       
        */
       
       
        153
       
       
       
       
        154
       
       
        private final File directory;
       
       
        155
       
       
        private final File journalFile;
       
       
        156
       
       
        private final File journalFileTmp;
       
       
        157
       
       
        private final int appVersion;
       
       
        158
       
       
        private final long maxSize;
       
       
        159
       
       
        private final int valueCount;
       
       
        160
       
       
        private long size = 0;
       
       
        161
       
       
        private Writer journalWriter;
       
       
        162
       
       
        private final LinkedHashMap<String, Entry> lruEntries
       
       
        163
       
       
        = new LinkedHashMap<String, Entry>(0, 0.75f, true);
       
       
        164
       
       
        private int redundantOpCount;
       
       
        165
       
       
       
       
        166
       
       
        /**
       
       
        167
       
       
        * To differentiate between old and current snapshots, each entry is given
       
       
        168
       
       
        * a sequence number each time an edit is committed. A snapshot is stale if
       
       
        169
       
       
        * its sequence number is not equal to its entry's sequence number.
       
       
        170
       
       
        */
       
       
        171
       
       
        private long nextSequenceNumber = 0;
       
       
        172
       
       
       
       
        173
       
       
        /* From java.util.Arrays */
       
       
        174
       
       
        @SuppressWarnings("unchecked")
       
       
        175
       
       
        private static <T> T[] copyOfRange(T[] original, int start, int end) {
       
       
        176
       
       
        final int originalLength = original.length; // For exception priority compatibility.
       
       
        177
       
       
        if (start > end) {
       
       
        178
       
       
        throw new IllegalArgumentException();
       
       
        179
       
       
        }
       
       
        180
       
       
        if (start < 0 || start > originalLength) {
       
       
        181
       
       
        throw new ArrayIndexOutOfBoundsException();
       
       
        182
       
       
        }
       
       
        183
       
       
        final int resultLength = end - start;
       
       
        184
       
       
        final int copyLength = Math.min(resultLength, originalLength - start);
       
       
        185
       
       
        final T[] result = (T[]) Array
       
       
        186
       
       
        .newInstance(original.getClass().getComponentType(), resultLength);
       
       
        187
       
       
        System.arraycopy(original, start, result, 0, copyLength);
       
       
        188
       
       
        return result;
       
       
        189
       
       
        }
       
       
        190
       
       
       
       
        191
       
       
        /**
       
       
        192
       
       
        * Returns the remainder of 'reader' as a string, closing it when done.
       
       
        193
       
       
        */
       
       
        194
       
       
        public static String readFully(Reader reader) throws IOException {
       
       
        195
       
       
        try {
       
       
        196
       
       
        StringWriter writer = new StringWriter();
       
       
        197
       
       
        char[] buffer = new char[1024];
       
       
        198
       
       
        int count;
       
       
        199
       
       
        while ((count = reader.read(buffer)) != -1) {
       
       
        200
       
       
        writer.write(buffer, 0, count);
       
       
        201
       
       
        }
       
       
        202
       
       
        return writer.toString();
       
       
        203
       
       
        } finally {
       
       
        204
       
       
        reader.close();
       
       
        205
       
       
        }
       
       
        206
       
       
        }
       
       
        207
       
       
       
       
        208
       
       
        /**
       
       
        209
       
       
        * Returns the ASCII characters up to but not including the next "\r\n", or
       
       
        210
       
       
        * "\n".
       
       
        211
       
       
        *
       
       
        212
       
       
        * @throws java.io.EOFException if the stream is exhausted before the next newline
       
       
        213
       
       
        *     character.
       
       
        214
       
       
        */
       
       
        215
       
       
        public static String readAsciiLine(InputStream in) throws IOException {
       
       
        216
       
       
        // TODO: support UTF-8 here instead
       
       
        217
       
       
       
       
        218
       
       
        StringBuilder result = new StringBuilder(80);
       
       
        219
       
       
        while (true) {
       
       
        220
       
       
        int c = in.read();
       
       
        221
       
       
        if (c == -1) {
       
       
        222
       
       
        throw new EOFException();
       
       
        223
       
       
        } else if (c == '\n') {
       
       
        224
       
       
        break;
       
       
        225
       
       
        }
       
       
        226
       
       
       
       
        227
       
       
        result.append((char) c);
       
       
        228
       
       
        }
       
       
        229
       
       
        int length = result.length();
       
       
        230
       
       
        if (length > 0 && result.charAt(length - 1) == '\r') {
       
       
        231
       
       
        result.setLength(length - 1);
       
       
        232
       
       
        }
       
       
        233
       
       
        return result.toString();
       
       
        234
       
       
        }
       
       
        235
       
       
       
       
        236
       
       
        /**
       
       
        237
       
       
        * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
       
       
        238
       
       
        */
       
       
        239
       
       
        public static void closeQuietly(Closeable closeable) {
       
       
        240
       
       
        if (closeable != null) {
       
       
        241
       
       
        try {
       
       
        242
       
       
        closeable.close();
       
       
        243
       
       
        } catch (RuntimeException rethrown) {
       
       
        244
       
       
        throw rethrown;
       
       
        245
       
       
        } catch (Exception ignored) {
       
       
        246
       
       
        }
       
       
        247
       
       
        }
       
       
        248
       
       
        }
       
       
        249
       
       
       
       
        250
       
       
        /**
       
       
        251
       
       
        * Recursively delete everything in {@code dir}.
       
       
        252
       
       
        */
       
       
        253
       
       
        // TODO: this should specify paths as Strings rather than as Files
       
       
        254
       
       
        public static void deleteContents(File dir) throws IOException {
       
       
        255
       
       
        File[] files = dir.listFiles();
       
       
        256
       
       
        if (files == null) {
       
       
        257
       
       
        throw new IllegalArgumentException("not a directory: " + dir);
       
       
        258
       
       
        }
       
       
        259
       
       
        for (File file : files) {
       
       
        260
       
       
        if (file.isDirectory()) {
       
       
        261
       
       
        deleteContents(file);
       
       
        262
       
       
        }
       
       
        263
       
       
        if (!file.delete()) {
       
       
        264
       
       
        throw new IOException("failed to delete file: " + file);
       
       
        265
       
       
        }
       
       
        266
       
       
        }
       
       
        267
       
       
        }
       
       
        268
       
       
       
       
        269
       
       
        /** This cache uses a single background thread to evict entries. */
       
       
        270
       
       
        private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
       
       
        271
       
       
        60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
       
       
        272
       
       
        private final Callable<Void> cleanupCallable = new Callable<Void>() {
       
       
        273
       
       
        @Override public Void call() throws Exception {
       
       
        274
       
       
        synchronized (DiskLruCache.this) {
       
       
        275
       
       
        if (journalWriter == null) {
       
       
        276
       
       
        return null; // closed
       
       
        277
       
       
        }
       
       
        278
       
       
        trimToSize();
       
       
        279
       
       
        if (journalRebuildRequired()) {
       
       
        280
       
       
        rebuildJournal();
       
       
        281
       
       
        redundantOpCount = 0;
       
       
        282
       
       
        }
       
       
        283
       
       
        }
       
       
        284
       
       
        return null;
       
       
        285
       
       
        }
       
       
        286
       
       
        };
       
       
        287
       
       
       
       
        288
       
       
        private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
       
       
        289
       
       
        this.directory = directory;
       
       
        290
       
       
        this.appVersion = appVersion;
       
       
        291
       
       
        this.journalFile = new File(directory, JOURNAL_FILE);
       
       
        292
       
       
        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
       
       
        293
       
       
        this.valueCount = valueCount;
       
       
        294
       
       
        this.maxSize = maxSize;
       
       
        295
       
       
        }
       
       
        296
       
       
       
       
        297
       
       
        /**
       
       
        298
       
       
        * Opens the cache in {@code directory}, creating a cache if none exists
       
       
        299
       
       
        * there.
       
       
        300
       
       
        *
       
       
        301
       
       
        * @param directory a writable directory
       
       
        302
       
       
        * @param appVersion
       
       
        303
       
       
        * @param valueCount the number of values per cache entry. Must be positive.
       
       
        304
       
       
        * @param maxSize the maximum number of bytes this cache should use to store
       
       
        305
       
       
        * @throws java.io.IOException if reading or writing the cache directory fails
       
       
        306
       
       
        */
       
       
        307
       
       
        public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
       
       
        308
       
       
        throws IOException {
       
       
        309
       
       
        if (maxSize <= 0) {
       
       
        310
       
       
        throw new IllegalArgumentException("maxSize <= 0");
       
       
        311
       
       
        }
       
       
        312
       
       
        if (valueCount <= 0) {
       
       
        313
       
       
        throw new IllegalArgumentException("valueCount <= 0");
       
       
        314
       
       
        }
       
       
        315
       
       
       
       
        316
       
       
        // prefer to pick up where we left off
       
       
        317
       
       
        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
       
       
        318
       
       
        if (cache.journalFile.exists()) {
       
       
        319
       
       
        try {
       
       
        320
       
       
        cache.readJournal();
       
       
        321
       
       
        cache.processJournal();
       
       
        322
       
       
        cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
       
       
        323
       
       
        IO_BUFFER_SIZE);
       
       
        324
       
       
        return cache;
       
       
        325
       
       
        } catch (IOException journalIsCorrupt) {
       
       
        326
       
       
        //                System.logW("DiskLruCache " + directory + " is corrupt: "
       
       
        327
       
       
        //                        + journalIsCorrupt.getMessage() + ", removing");
       
       
        328
       
       
        cache.delete();
       
       
        329
       
       
        }
       
       
        330
       
       
        }
       
       
        331
       
       
       
       
        332
       
       
        // create a new empty cache
       
       
        333
       
       
        directory.mkdirs();
       
       
        334
       
       
        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
       
       
        335
       
       
        cache.rebuildJournal();
       
       
        336
       
       
        return cache;
       
       
        337
       
       
        }
       
       
        338
       
       
       
       
        339
       
       
        private void readJournal() throws IOException {
       
       
        340
       
       
        InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
       
       
        341
       
       
        try {
       
       
        342
       
       
        String magic = readAsciiLine(in);
       
       
        343
       
       
        String version = readAsciiLine(in);
       
       
        344
       
       
        String appVersionString = readAsciiLine(in);
       
       
        345
       
       
        String valueCountString = readAsciiLine(in);
       
       
        346
       
       
        String blank = readAsciiLine(in);
       
       
        347
       
       
        if (!MAGIC.equals(magic)
       
       
        348
       
       
        || !VERSION_1.equals(version)
       
       
        349
       
       
        || !Integer.toString(appVersion).equals(appVersionString)
       
       
        350
       
       
        || !Integer.toString(valueCount).equals(valueCountString)
       
       
        351
       
       
        || !"".equals(blank)) {
       
       
        352
       
       
        throw new IOException("unexpected journal header: ["
       
       
        353
       
       
        + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
       
       
        354
       
       
        }
       
       
        355
       
       
       
       
        356
       
       
        while (true) {
       
       
        357
       
       
        try {
       
       
        358
       
       
        readJournalLine(readAsciiLine(in));
       
       
        359
       
       
        } catch (EOFException endOfJournal) {
       
       
        360
       
       
        break;
       
       
        361
       
       
        }
       
       
        362
       
       
        }
       
       
        363
       
       
        } finally {
       
       
        364
       
       
        closeQuietly(in);
       
       
        365
       
       
        }
       
       
        366
       
       
        }
       
       
        367
       
       
       
       
        368
       
       
        private void readJournalLine(String line) throws IOException {
       
       
        369
       
       
        String[] parts = line.split(" ");
       
       
        370
       
       
        if (parts.length < 2) {
       
       
        371
       
       
        throw new IOException("unexpected journal line: " + line);
       
       
        372
       
       
        }
       
       
        373
       
       
       
       
        374
       
       
        String key = parts[1];
       
       
        375
       
       
        if (parts[0].equals(REMOVE) && parts.length == 2) {
       
       
        376
       
       
        lruEntries.remove(key);
       
       
        377
       
       
        return;
       
       
        378
       
       
        }
       
       
        379
       
       
       
       
        380
       
       
        Entry entry = lruEntries.get(key);
       
       
        381
       
       
        if (entry == null) {
       
       
        382
       
       
        entry = new Entry(key);
       
       
        383
       
       
        lruEntries.put(key, entry);
       
       
        384
       
       
        }
       
       
        385
       
       
       
       
        386
       
       
        if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
       
       
        387
       
       
        entry.readable = true;
       
       
        388
       
       
        entry.currentEditor = null;
       
       
        389
       
       
        entry.setLengths(copyOfRange(parts, 2, parts.length));
       
       
        390
       
       
        } else if (parts[0].equals(DIRTY) && parts.length == 2) {
       
       
        391
       
       
        entry.currentEditor = new Editor(entry);
       
       
        392
       
       
        } else if (parts[0].equals(READ) && parts.length == 2) {
       
       
        393
       
       
        // this work was already done by calling lruEntries.get()
       
       
        394
       
       
        } else {
       
       
        395
       
       
        throw new IOException("unexpected journal line: " + line);
       
       
        396
       
       
        }
       
       
        397
       
       
        }
       
       
        398
       
       
       
       
        399
       
       
        /**
       
       
        400
       
       
        * Computes the initial size and collects garbage as a part of opening the
       
       
        401
       
       
        * cache. Dirty entries are assumed to be inconsistent and will be deleted.
       
       
        402
       
       
        */
       
       
        403
       
       
        private void processJournal() throws IOException {
       
       
        404
       
       
        deleteIfExists(journalFileTmp);
       
       
        405
       
       
        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
       
       
        406
       
       
        Entry entry = i.next();
       
       
        407
       
       
        if (entry.currentEditor == null) {
       
       
        408
       
       
        for (int t = 0; t < valueCount; t++) {
       
       
        409
       
       
        size += entry.lengths[t];
       
       
        410
       
       
        }
       
       
        411
       
       
        } else {
       
       
        412
       
       
        entry.currentEditor = null;
       
       
        413
       
       
        for (int t = 0; t < valueCount; t++) {
       
       
        414
       
       
        deleteIfExists(entry.getCleanFile(t));
       
       
        415
       
       
        deleteIfExists(entry.getDirtyFile(t));
       
       
        416
       
       
        }
       
       
        417
       
       
        i.remove();
       
       
        418
       
       
        }
       
       
        419
       
       
        }
       
       
        420
       
       
        }
       
       
        421
       
       
       
       
        422
       
       
        /**
       
       
        423
       
       
        * Creates a new journal that omits redundant information. This replaces the
       
       
        424
       
       
        * current journal if it exists.
       
       
        425
       
       
        */
       
       
        426
       
       
        private synchronized void rebuildJournal() throws IOException {
       
       
        427
       
       
        if (journalWriter != null) {
       
       
        428
       
       
        journalWriter.close();
       
       
        429
       
       
        }
       
       
        430
       
       
       
       
        431
       
       
        Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
       
       
        432
       
       
        writer.write(MAGIC);
       
       
        433
       
       
        writer.write("\n");
       
       
        434
       
       
        writer.write(VERSION_1);
       
       
        435
       
       
        writer.write("\n");
       
       
        436
       
       
        writer.write(Integer.toString(appVersion));
       
       
        437
       
       
        writer.write("\n");
       
       
        438
       
       
        writer.write(Integer.toString(valueCount));
       
       
        439
       
       
        writer.write("\n");
       
       
        440
       
       
        writer.write("\n");
       
       
        441
       
       
       
       
        442
       
       
        for (Entry entry : lruEntries.values()) {
       
       
        443
       
       
        if (entry.currentEditor != null) {
       
       
        444
       
       
        writer.write(DIRTY + ' ' + entry.key + '\n');
       
       
        445
       
       
        } else {
       
       
        446
       
       
        writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
       
       
        447
       
       
        }
       
       
        448
       
       
        }
       
       
        449
       
       
       
       
        450
       
       
        writer.close();
       
       
        451
       
       
        journalFileTmp.renameTo(journalFile);
       
       
        452
       
       
        journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
       
       
        453
       
       
        }
       
       
        454
       
       
       
       
        455
       
       
        private static void deleteIfExists(File file) throws IOException {
       
       
        456
       
       
        //        try {
       
       
        457
       
       
        //            Libcore.os.remove(file.getPath());
       
       
        458
       
       
        //        } catch (ErrnoException errnoException) {
       
       
        459
       
       
        //            if (errnoException.errno != OsConstants.ENOENT) {
       
       
        460
       
       
        //                throw errnoException.rethrowAsIOException();
       
       
        461
       
       
        //            }
       
       
        462
       
       
        //        }
       
       
        463
       
       
        if (file.exists() && !file.delete()) {
       
       
        464
       
       
        throw new IOException();
       
       
        465
       
       
        }
       
       
        466
       
       
        }
       
       
        467
       
       
       
       
        468
       
       
        /**
       
       
        469
       
       
        * Returns a snapshot of the entry named {@code key}, or null if it doesn't
       
       
        470
       
       
        * exist is not currently readable. If a value is returned, it is moved to
       
       
        471
       
       
        * the head of the LRU queue.
       
       
        472
       
       
        */
       
       
        473
       
       
        public synchronized Snapshot get(String key) throws IOException {
       
       
        474
       
       
        checkNotClosed();
       
       
        475
       
       
        validateKey(key);
       
       
        476
       
       
        Entry entry = lruEntries.get(key);
       
       
        477
       
       
        if (entry == null) {
       
       
        478
       
       
        return null;
       
       
        479
       
       
        }
       
       
        480
       
       
       
       
        481
       
       
        if (!entry.readable) {
       
       
        482
       
       
        return null;
       
       
        483
       
       
        }
       
       
        484
       
       
       
       
        485
       
       
        /*
       
       
        486
       
       
        * Open all streams eagerly to guarantee that we see a single published
       
       
        487
       
       
        * snapshot. If we opened streams lazily then the streams could come
       
       
        488
       
       
        * from different edits.
       
       
        489
       
       
        */
       
       
        490
       
       
        InputStream[] ins = new InputStream[valueCount];
       
       
        491
       
       
        try {
       
       
        492
       
       
        for (int i = 0; i < valueCount; i++) {
       
       
        493
       
       
        ins[i] = new FileInputStream(entry.getCleanFile(i));
       
       
        494
       
       
        }
       
       
        495
       
       
        } catch (FileNotFoundException e) {
       
       
        496
       
       
        // a file must have been deleted manually!
       
       
        497
       
       
        return null;
       
       
        498
       
       
        }
       
       
        499
       
       
       
       
        500
       
       
        redundantOpCount++;
       
       
        501
       
       
        journalWriter.append(READ + ' ' + key + '\n');
       
       
        502
       
       
        if (journalRebuildRequired()) {
       
       
        503
       
       
        executorService.submit(cleanupCallable);
       
       
        504
       
       
        }
       
       
        505
       
       
       
       
        506
       
       
        return new Snapshot(key, entry.sequenceNumber, ins);
       
       
        507
       
       
        }
       
       
        508
       
       
       
       
        509
       
       
        /**
       
       
        510
       
       
        * Returns an editor for the entry named {@code key}, or null if another
       
       
        511
       
       
        * edit is in progress.
       
       
        512
       
       
        */
       
       
        513
       
       
        public Editor edit(String key) throws IOException {
       
       
        514
       
       
        return edit(key, ANY_SEQUENCE_NUMBER);
       
       
        515
       
       
        }
       
       
        516
       
       
       
       
        517
       
       
        private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
       
       
        518
       
       
        checkNotClosed();
       
       
        519
       
       
        validateKey(key);
       
       
        520
       
       
        Entry entry = lruEntries.get(key);
       
       
        521
       
       
        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
       
       
        522
       
       
        && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
       
       
        523
       
       
        return null; // snapshot is stale
       
       
        524
       
       
        }
       
       
        525
       
       
        if (entry == null) {
       
       
        526
       
       
        entry = new Entry(key);
       
       
        527
       
       
        lruEntries.put(key, entry);
       
       
        528
       
       
        } else if (entry.currentEditor != null) {
       
       
        529
       
       
        return null; // another edit is in progress
       
       
        530
       
       
        }
       
       
        531
       
       
       
       
        532
       
       
        Editor editor = new Editor(entry);
       
       
        533
       
       
        entry.currentEditor = editor;
       
       
        534
       
       
       
       
        535
       
       
        // flush the journal before creating files to prevent file leaks
       
       
        536
       
       
        journalWriter.write(DIRTY + ' ' + key + '\n');
       
       
        537
       
       
        journalWriter.flush();
       
       
        538
       
       
        return editor;
       
       
        539
       
       
        }
       
       
        540
       
       
       
       
        541
       
       
        /**
       
       
        542
       
       
        * Returns the directory where this cache stores its data.
       
       
        543
       
       
        */
       
       
        544
       
       
        public File getDirectory() {
       
       
        545
       
       
        return directory;
       
       
        546
       
       
        }
       
       
        547
       
       
       
       
        548
       
       
        /**
       
       
        549
       
       
        * Returns the maximum number of bytes that this cache should use to store
       
       
        550
       
       
        * its data.
       
       
        551
       
       
        */
       
       
        552
       
       
        public long maxSize() {
       
       
        553
       
       
        return maxSize;
       
       
        554
       
       
        }
       
       
        555
       
       
       
       
        556
       
       
        /**
       
       
        557
       
       
        * Returns the number of bytes currently being used to store the values in
       
       
        558
       
       
        * this cache. This may be greater than the max size if a background
       
       
        559
       
       
        * deletion is pending.
       
       
        560
       
       
        */
       
       
        561
       
       
        public synchronized long size() {
       
       
        562
       
       
        return size;
       
       
        563
       
       
        }
       
       
        564
       
       
       
       
        565
       
       
        private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
       
       
        566
       
       
        Entry entry = editor.entry;
       
       
        567
       
       
        if (entry.currentEditor != editor) {
       
       
        568
       
       
        throw new IllegalStateException();
       
       
        569
       
       
        }
       
       
        570
       
       
       
       
        571
       
       
        // if this edit is creating the entry for the first time, every index must have a value
       
       
        572
       
       
        if (success && !entry.readable) {
       
       
        573
       
       
        for (int i = 0; i < valueCount; i++) {
       
       
        574
       
       
        if (!entry.getDirtyFile(i).exists()) {
       
       
        575
       
       
        editor.abort();
       
       
        576
       
       
        throw new IllegalStateException("edit didn't create file " + i);
       
       
        577
       
       
        }
       
       
        578
       
       
        }
       
       
        579
       
       
        }
       
       
        580
       
       
       
       
        581
       
       
        for (int i = 0; i < valueCount; i++) {
       
       
        582
       
       
        File dirty = entry.getDirtyFile(i);
       
       
        583
       
       
        if (success) {
       
       
        584
       
       
        if (dirty.exists()) {
       
       
        585
       
       
        File clean = entry.getCleanFile(i);
       
       
        586
       
       
        dirty.renameTo(clean);
       
       
        587
       
       
        long oldLength = entry.lengths[i];
       
       
        588
       
       
        long newLength = clean.length();
       
       
        589
       
       
        entry.lengths[i] = newLength;
       
       
        590
       
       
        size = size - oldLength + newLength;
       
       
        591
       
       
        }
       
       
        592
       
       
        } else {
       
       
        593
       
       
        deleteIfExists(dirty);
       
       
        594
       
       
        }
       
       
        595
       
       
        }
       
       
        596
       
       
       
       
        597
       
       
        redundantOpCount++;
       
       
        598
       
       
        entry.currentEditor = null;
       
       
        599
       
       
        if (entry.readable | success) {
       
       
        600
       
       
        entry.readable = true;
       
       
        601
       
       
        journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
       
       
        602
       
       
        if (success) {
       
       
        603
       
       
        entry.sequenceNumber = nextSequenceNumber++;
       
       
        604
       
       
        }
       
       
        605
       
       
        } else {
       
       
        606
       
       
        lruEntries.remove(entry.key);
       
       
        607
       
       
        journalWriter.write(REMOVE + ' ' + entry.key + '\n');
       
       
        608
       
       
        }
       
       
        609
       
       
       
       
        610
       
       
        if (size > maxSize || journalRebuildRequired()) {
       
       
        611
       
       
        executorService.submit(cleanupCallable);
       
       
        612
       
       
        }
       
       
        613
       
       
        }
       
       
        614
       
       
       
       
        615
       
       
        /**
       
       
        616
       
       
        * We only rebuild the journal when it will halve the size of the journal
       
       
        617
       
       
        * and eliminate at least 2000 ops.
       
       
        618
       
       
        */
       
       
        619
       
       
        private boolean journalRebuildRequired() {
       
       
        620
       
       
        final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
       
       
        621
       
       
        return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
       
       
        622
       
       
        && redundantOpCount >= lruEntries.size();
       
       
        623
       
       
        }
       
       
        624
       
       
       
       
        625
       
       
        /**
       
       
        626
       
       
        * Drops the entry for {@code key} if it exists and can be removed. Entries
       
       
        627
       
       
        * actively being edited cannot be removed.
       
       
        628
       
       
        *
       
       
        629
       
       
        * @return true if an entry was removed.
       
       
        630
       
       
        */
       
       
        631
       
       
        public synchronized boolean remove(String key) throws IOException {
       
       
        632
       
       
        checkNotClosed();
       
       
        633
       
       
        validateKey(key);
       
       
        634
       
       
        Entry entry = lruEntries.get(key);
       
       
        635
       
       
        if (entry == null || entry.currentEditor != null) {
       
       
        636
       
       
        return false;
       
       
        637
       
       
        }
       
       
        638
       
       
       
       
        639
       
       
        for (int i = 0; i < valueCount; i++) {
       
       
        640
       
       
        File file = entry.getCleanFile(i);
       
       
        641
       
       
        if (!file.delete()) {
       
       
        642
       
       
        throw new IOException("failed to delete " + file);
       
       
        643
       
       
        }
       
       
        644
       
       
        size -= entry.lengths[i];
       
       
        645
       
       
        entry.lengths[i] = 0;
       
       
        646
       
       
        }
       
       
        647
       
       
       
       
        648
       
       
        redundantOpCount++;
       
       
        649
       
       
        journalWriter.append(REMOVE + ' ' + key + '\n');
       
       
        650
       
       
        lruEntries.remove(key);
       
       
        651
       
       
       
       
        652
       
       
        if (journalRebuildRequired()) {
       
       
        653
       
       
        executorService.submit(cleanupCallable);
       
       
        654
       
       
        }
       
       
        655
       
       
       
       
        656
       
       
        return true;
       
       
        657
       
       
        }
       
       
        658
       
       
       
       
        659
       
       
        /**
       
       
        660
       
       
        * Returns true if this cache has been closed.
       
       
        661
       
       
        */
       
       
        662
       
       
        public boolean isClosed() {
       
       
        663
       
       
        return journalWriter == null;
       
       
        664
       
       
        }
       
       
        665
       
       
       
       
        666
       
       
        private void checkNotClosed() {
       
       
        667
       
       
        if (journalWriter == null) {
       
       
        668
       
       
        throw new IllegalStateException("cache is closed");
       
       
        669
       
       
        }
       
       
        670
       
       
        }
       
       
        671
       
       
       
       
        672
       
       
        /**
       
       
        673
       
       
        * Force buffered operations to the filesystem.
       
       
        674
       
       
        */
       
       
        675
       
       
        public synchronized void flush() throws IOException {
       
       
        676
       
       
        checkNotClosed();
       
       
        677
       
       
        trimToSize();
       
       
        678
       
       
        journalWriter.flush();
       
       
        679
       
       
        }
       
       
        680
       
       
       
       
        681
       
       
        /**
       
       
        682
       
       
        * Closes this cache. Stored values will remain on the filesystem.
       
       
        683
       
       
        */
       
       
        684
       
       
        public synchronized void close() throws IOException {
       
       
        685
       
       
        if (journalWriter == null) {
       
       
        686
       
       
        return; // already closed
       
       
        687
       
       
        }
       
       
        688
       
       
        for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
       
       
        689
       
       
        if (entry.currentEditor != null) {
       
       
        690
       
       
        entry.currentEditor.abort();
       
       
        691
       
       
        }
       
       
        692
       
       
        }
       
       
        693
       
       
        trimToSize();
       
       
        694
       
       
        journalWriter.close();
       
       
        695
       
       
        journalWriter = null;
       
       
        696
       
       
        }
       
       
        697
       
       
       
       
        698
       
       
        private void trimToSize() throws IOException {
       
       
        699
       
       
        while (size > maxSize) {
       
       
        700
       
       
        //            Map.Entry<String, Entry> toEvict = lruEntries.eldest();
       
       
        701
       
       
        final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
       
       
        702
       
       
        remove(toEvict.getKey());
       
       
        703
       
       
        }
       
       
        704
       
       
        }
       
       
        705
       
       
       
       
        706
       
       
        /**
       
       
        707
       
       
        * Closes the cache and deletes all of its stored values. This will delete
       
       
        708
       
       
        * all files in the cache directory including files that weren't created by
       
       
        709
       
       
        * the cache.
       
       
        710
       
       
        */
       
       
        711
       
       
        public void delete() throws IOException {
       
       
        712
       
       
        close();
       
       
        713
       
       
        deleteContents(directory);
       
       
        714
       
       
        }
       
       
        715
       
       
       
       
        716
       
       
        private void validateKey(String key) {
       
       
        717
       
       
        if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
       
       
        718
       
       
        throw new IllegalArgumentException(
       
       
        719
       
       
        "keys must not contain spaces or newlines: \"" + key + "\"");
       
       
        720
       
       
        }
       
       
        721
       
       
        }
       
       
        722
       
       
       
       
        723
       
       
        private static String inputStreamToString(InputStream in) throws IOException {
       
       
        724
       
       
        return readFully(new InputStreamReader(in, UTF_8));
       
       
        725
       
       
        }
       
       
        726
       
       
       
       
        727
       
       
        /**
       
       
        728
       
       
        * A snapshot of the values for an entry.
       
       
        729
       
       
        */
       
       
        730
       
       
        public final class Snapshot implements Closeable {
       
       
        731
       
       
        private final String key;
       
       
        732
       
       
        private final long sequenceNumber;
       
       
        733
       
       
        private final InputStream[] ins;
       
       
        734
       
       
       
       
        735
       
       
        private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
       
       
        736
       
       
        this.key = key;
       
       
        737
       
       
        this.sequenceNumber = sequenceNumber;
       
       
        738
       
       
        this.ins = ins;
       
       
        739
       
       
        }
       
       
        740
       
       
       
       
        741
       
       
        /**
       
       
        742
       
       
        * Returns an editor for this snapshot's entry, or null if either the
       
       
        743
       
       
        * entry has changed since this snapshot was created or if another edit
       
       
        744
       
       
        * is in progress.
       
       
        745
       
       
        */
       
       
        746
       
       
        public Editor edit() throws IOException {
       
       
        747
       
       
        return DiskLruCache.this.edit(key, sequenceNumber);
       
       
        748
       
       
        }
       
       
        749
       
       
       
       
        750
       
       
        /**
       
       
        751
       
       
        * Returns the unbuffered stream with the value for {@code index}.
       
       
        752
       
       
        */
       
       
        753
       
       
        public InputStream getInputStream(int index) {
       
       
        754
       
       
        return ins[index];
       
       
        755
       
       
        }
       
       
        756
       
       
       
       
        757
       
       
        /**
       
       
        758
       
       
        * Returns the string value for {@code index}.
       
       
        759
       
       
        */
       
       
        760
       
       
        public String getString(int index) throws IOException {
       
       
        761
       
       
        return inputStreamToString(getInputStream(index));
       
       
        762
       
       
        }
       
       
        763
       
       
       
       
        764
       
       
        @Override public void close() {
       
       
        765
       
       
        for (InputStream in : ins) {
       
       
        766
       
       
        closeQuietly(in);
       
       
        767
       
       
        }
       
       
        768
       
       
        }
       
       
        769
       
       
        }
       
       
        770
       
       
       
       
        771
       
       
        /**
       
       
        772
       
       
        * Edits the values for an entry.
       
       
        773
       
       
        */
       
       
        774
       
       
        public final class Editor {
       
       
        775
       
       
        private final Entry entry;
       
       
        776
       
       
        private boolean hasErrors;
       
       
        777
       
       
       
       
        778
       
       
        private Editor(Entry entry) {
       
       
        779
       
       
        this.entry = entry;
       
       
        780
       
       
        }
       
       
        781
       
       
       
       
        782
       
       
        /**
       
       
        783
       
       
        * Returns an unbuffered input stream to read the last committed value,
       
       
        784
       
       
        * or null if no value has been committed.
       
       
        785
       
       
        */
       
       
        786
       
       
        public InputStream newInputStream(int index) throws IOException {
       
       
        787
       
       
        synchronized (DiskLruCache.this) {
       
       
        788
       
       
        if (entry.currentEditor != this) {
       
       
        789
       
       
        throw new IllegalStateException();
       
       
        790
       
       
        }
       
       
        791
       
       
        if (!entry.readable) {
       
       
        792
       
       
        return null;
       
       
        793
       
       
        }
       
       
        794
       
       
        return new FileInputStream(entry.getCleanFile(index));
       
       
        795
       
       
        }
       
       
        796
       
       
        }
       
       
        797
       
       
       
       
        798
       
       
        /**
       
       
        799
       
       
        * Returns the last committed value as a string, or null if no value
       
       
        800
       
       
        * has been committed.
       
       
        801
       
       
        */
       
       
        802
       
       
        public String getString(int index) throws IOException {
       
       
        803
       
       
        InputStream in = newInputStream(index);
       
       
        804
       
       
        return in != null ? inputStreamToString(in) : null;
       
       
        805
       
       
        }
       
       
        806
       
       
       
       
        807
       
       
        /**
       
       
        808
       
       
        * Returns a new unbuffered output stream to write the value at
       
       
        809
       
       
        * {@code index}. If the underlying output stream encounters errors
       
       
        810
       
       
        * when writing to the filesystem, this edit will be aborted when
       
       
        811
       
       
        * {@link #commit} is called. The returned output stream does not throw
       
       
        812
       
       
        * IOExceptions.
       
       
        813
       
       
        */
       
       
        814
       
       
        public OutputStream newOutputStream(int index) throws IOException {
       
       
        815
       
       
        synchronized (DiskLruCache.this) {
       
       
        816
       
       
        if (entry.currentEditor != this) {
       
       
        817
       
       
        throw new IllegalStateException();
       
       
        818
       
       
        }
       
       
        819
       
       
        return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
       
       
        820
       
       
        }
       
       
        821
       
       
        }
       
       
        822
       
       
       
       
        823
       
       
        /**
       
       
        824
       
       
        * Sets the value at {@code index} to {@code value}.
       
       
        825
       
       
        */
       
       
        826
       
       
        public void set(int index, String value) throws IOException {
       
       
        827
       
       
        Writer writer = null;
       
       
        828
       
       
        try {
       
       
        829
       
       
        writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
       
       
        830
       
       
        writer.write(value);
       
       
        831
       
       
        } finally {
       
       
        832
       
       
        closeQuietly(writer);
       
       
        833
       
       
        }
       
       
        834
       
       
        }
       
       
        835
       
       
       
       
        836
       
       
        /**
       
       
        837
       
       
        * Commits this edit so it is visible to readers.  This releases the
       
       
        838
       
       
        * edit lock so another edit may be started on the same key.
       
       
        839
       
       
        */
       
       
        840
       
       
        public void commit() throws IOException {
       
       
        841
       
       
        if (hasErrors) {
       
       
        842
       
       
        completeEdit(this, false);
       
       
        843
       
       
        remove(entry.key); // the previous entry is stale
       
       
        844
       
       
        } else {
       
       
        845
       
       
        completeEdit(this, true);
       
       
        846
       
       
        }
       
       
        847
       
       
        }
       
       
        848
       
       
       
       
        849
       
       
        /**
       
       
        850
       
       
        * Aborts this edit. This releases the edit lock so another edit may be
       
       
        851
       
       
        * started on the same key.
       
       
        852
       
       
        */
       
       
        853
       
       
        public void abort() throws IOException {
       
       
        854
       
       
        completeEdit(this, false);
       
       
        855
       
       
        }
       
       
        856
       
       
       
       
        857
       
       
        private class FaultHidingOutputStream extends FilterOutputStream {
       
       
        858
       
       
        private FaultHidingOutputStream(OutputStream out) {
       
       
        859
       
       
        super(out);
       
       
        860
       
       
        }
       
       
        861
       
       
       
       
        862
       
       
        @Override public void write(int oneByte) {
       
       
        863
       
       
        try {
       
       
        864
       
       
        out.write(oneByte);
       
       
        865
       
       
        } catch (IOException e) {
       
       
        866
       
       
        hasErrors = true;
       
       
        867
       
       
        }
       
       
        868
       
       
        }
       
       
        869
       
       
       
       
        870
       
       
        @Override public void write(byte[] buffer, int offset, int length) {
       
       
        871
       
       
        try {
       
       
        872
       
       
        out.write(buffer, offset, length);
       
       
        873
       
       
        } catch (IOException e) {
       
       
        874
       
       
        hasErrors = true;
       
       
        875
       
       
        }
       
       
        876
       
       
        }
       
       
        877
       
       
       
       
        878
       
       
        @Override public void close() {
       
       
        879
       
       
        try {
       
       
        880
       
       
        out.close();
       
       
        881
       
       
        } catch (IOException e) {
       
       
        882
       
       
        hasErrors = true;
       
       
        883
       
       
        }
       
       
        884
       
       
        }
       
       
        885
       
       
       
       
        886
       
       
        @Override public void flush() {
       
       
        887
       
       
        try {
       
       
        888
       
       
        out.flush();
       
       
        889
       
       
        } catch (IOException e) {
       
       
        890
       
       
        hasErrors = true;
       
       
        891
       
       
        }
       
       
        892
       
       
        }
       
       
        893
       
       
        }
       
       
        894
       
       
        }
       
       
        895
       
       
       
       
        896
       
       
        private final class Entry {
       
       
        897
       
       
        private final String key;
       
       
        898
       
       
       
       
        899
       
       
        /** Lengths of this entry's files. */
       
       
        900
       
       
        private final long[] lengths;
       
       
        901
       
       
       
       
        902
       
       
        /** True if this entry has ever been published */
       
       
        903
       
       
        private boolean readable;
       
       
        904
       
       
       
       
        905
       
       
        /** The ongoing edit or null if this entry is not being edited. */
       
       
        906
       
       
        private Editor currentEditor;
       
       
        907
       
       
       
       
        908
       
       
        /** The sequence number of the most recently committed edit to this entry. */
       
       
        909
       
       
        private long sequenceNumber;
       
       
        910
       
       
       
       
        911
       
       
        private Entry(String key) {
       
       
        912
       
       
        this.key = key;
       
       
        913
       
       
        this.lengths = new long[valueCount];
       
       
        914
       
       
        }
       
       
        915
       
       
       
       
        916
       
       
        public String getLengths() throws IOException {
       
       
        917
       
       
        StringBuilder result = new StringBuilder();
       
       
        918
       
       
        for (long size : lengths) {
       
       
        919
       
       
        result.append(' ').append(size);
       
       
        920
       
       
        }
       
       
        921
       
       
        return result.toString();
       
       
        922
       
       
        }
       
       
        923
       
       
       
       
        924
       
       
        /**
       
       
        925
       
       
        * Set lengths using decimal numbers like "10123".
       
       
        926
       
       
        */
       
       
        927
       
       
        private void setLengths(String[] strings) throws IOException {
       
       
        928
       
       
        if (strings.length != valueCount) {
       
       
        929
       
       
        throw invalidLengths(strings);
       
       
        930
       
       
        }
       
       
        931
       
       
       
       
        932
       
       
        try {
       
       
        933
       
       
        for (int i = 0; i < strings.length; i++) {
       
       
        934
       
       
        lengths[i] = Long.parseLong(strings[i]);
       
       
        935
       
       
        }
       
       
        936
       
       
        } catch (NumberFormatException e) {
       
       
        937
       
       
        throw invalidLengths(strings);
       
       
        938
       
       
        }
       
       
        939
       
       
        }
       
       
        940
       
       
       
       
        941
       
       
        private IOException invalidLengths(String[] strings) throws IOException {
       
       
        942
       
       
        throw new IOException("unexpected journal line: " + Arrays.toString(strings));
       
       
        943
       
       
        }
       
       
        944
       
       
       
       
        945
       
       
        public File getCleanFile(int i) {
       
       
        946
       
       
        return new File(directory, key + "." + i);
       
       
        947
       
       
        }
       
       
        948
       
       
       
       
        949
       
       
        public File getDirtyFile(int i) {
       
       
        950
       
       
        return new File(directory, key + "." + i + ".tmp");
       
       
        951
       
       
        }
       
       
        952
       
       
        }
       
       
        953
       
       
        }